Skip to content

Commit

Permalink
Merge pull request #55 from MinaFoundation/health-ep
Browse files Browse the repository at this point in the history
PM-1939 - Health endpoint
  • Loading branch information
piotr-iohk authored Sep 5, 2024
2 parents f29354f + 5ded609 commit 0aabeda
Show file tree
Hide file tree
Showing 13 changed files with 139 additions and 40 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ on:
push:
branches:
- main
tags:
- '*'
# tags:
# - '*'
pull_request:
branches:
- main
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ on:
push:
branches:
- main
tags:
- '*'
# tags:
# - '*'
pull_request:
branches:
- main
Expand Down Expand Up @@ -45,6 +45,7 @@ jobs:
# UPTIME_SERVICE_SECRET is a passphrase needed to decrypt uptime service config files
UPTIME_SERVICE_SECRET: ${{ secrets.UPTIME_SERVICE_SECRET }}
run: nix-shell --run "make integration-test"
timeout-minutes: 30

- name: 📖 Get logs
if: always()
Expand All @@ -55,7 +56,6 @@ jobs:
minimina node logs -n integration-test -i uptime-service-backend -r > integration-test/logs/uptime-service-backend.log
minimina node logs -n integration-test -i node-a -r > integration-test/logs/node-a.log
minimina node logs -n integration-test -i node-b -r > integration-test/logs/node-b.log
minimina node logs -n integration-test -i node-c -r > integration-test/logs/node-c.log
- name: 📎 Upload logs
uses: actions/upload-artifact@v3
Expand Down
7 changes: 7 additions & 0 deletions src/cmd/delegation_backend/main_bpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func main() {
ctx := context.Background()
appCfg := LoadEnv(log)
app := new(App)
app.IsReady = false
app.Log = log
awsctx := AwsContext{}
kc := KeyspaceContext{}
Expand Down Expand Up @@ -118,6 +119,11 @@ func main() {
})
http.Handle("/v1/submit", app.NewSubmitH())

// Health check endpoint
http.HandleFunc("/health", HealthHandler(func() bool {
return app.IsReady
}))

// Sheets service and whitelist loop
app.WhitelistDisabled = appCfg.DelegationWhitelistDisabled
if app.WhitelistDisabled {
Expand Down Expand Up @@ -150,5 +156,6 @@ func main() {
}

// Start server
app.IsReady = true
log.Fatal(http.ListenAndServe(DELEGATION_BACKEND_LISTEN_TO, nil))
}
24 changes: 24 additions & 0 deletions src/delegation_backend/health.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package delegation_backend

import (
"encoding/json"
"net/http"
)

// HealthStatus represents the JSON response structure for the /health endpoint
type HealthStatus struct {
Status string `json:"status"`
}

// HealthHandler handles the /health endpoint, checking if the application is ready.
func HealthHandler(isReady func() bool) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
if isReady() {
rw.WriteHeader(http.StatusOK)
json.NewEncoder(rw).Encode(HealthStatus{Status: "ok"})
} else {
rw.WriteHeader(http.StatusServiceUnavailable)
json.NewEncoder(rw).Encode(HealthStatus{Status: "unavailable"})
}
}
}
81 changes: 81 additions & 0 deletions src/delegation_backend/health_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package delegation_backend

import (
"net/http"
"net/http/httptest"
"testing"
)

// Mock App structure with IsReady flag
type MockApp struct {
IsReady bool
}

// TestHealthEndpointBeforeReady tests the /health endpoint before the application is ready.
func TestHealthEndpointBeforeReady(t *testing.T) {
app := &MockApp{IsReady: false}

req, err := http.NewRequest("GET", "/health", nil)
if err != nil {
t.Fatal(err)
}

rr := httptest.NewRecorder()
handler := HealthHandler(func() bool { return app.IsReady })

handler.ServeHTTP(rr, req)

if status := rr.Code; status != http.StatusServiceUnavailable {
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusServiceUnavailable)
}

}

// TestHealthEndpointAfterReady tests the /health endpoint after the application is ready.
func TestHealthEndpointAfterReady(t *testing.T) {
app := &MockApp{IsReady: true}

req, err := http.NewRequest("GET", "/health", nil)
if err != nil {
t.Fatal(err)
}

rr := httptest.NewRecorder()
handler := HealthHandler(func() bool { return app.IsReady })

handler.ServeHTTP(rr, req)

if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
}

}

// TestHealthEndpointTransition tests the /health endpoint during the transition from not ready to ready.
func TestHealthEndpointTransition(t *testing.T) {
app := &MockApp{IsReady: false}

req, err := http.NewRequest("GET", "/health", nil)
if err != nil {
t.Fatal(err)
}

rr := httptest.NewRecorder()
handler := HealthHandler(func() bool { return app.IsReady })

// Initially not ready
handler.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusServiceUnavailable {
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusServiceUnavailable)
}

// Simulate the application becoming ready
app.IsReady = true
rr = httptest.NewRecorder() // Reset the recorder
handler.ServeHTTP(rr, req)

if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
}

}
1 change: 1 addition & 0 deletions src/delegation_backend/submit.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ type App struct {
NetworkId uint8
Save func(ObjectsToSave)
Now nowFunc
IsReady bool
}

type SubmitH struct {
Expand Down
10 changes: 5 additions & 5 deletions src/integration_tests/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,6 @@ func TestIntegration_BP_Uptime_Storage(t *testing.T) {
miniminaNetworkStart(networkName)
defer miniminaNetworkStop(networkName)

err = WaitUntilPostgresHasSubmissions(postgresConn)
if err != nil {
t.Fatalf("Failed to wait until Postgres has submissions: %v", err)
}

err = waitUntilLocalStorageHasBlocksAndSubmissions(uptimeStorageDir)
defer emptyLocalFilesystemStorage(uptimeStorageDir)
if err != nil {
Expand All @@ -88,6 +83,11 @@ func TestIntegration_BP_Uptime_Storage(t *testing.T) {
t.Fatalf("Failed to wait until S3 bucket is not empty: %v", err)
}

err = WaitUntilPostgresHasSubmissions(postgresConn)
if err != nil {
t.Fatalf("Failed to wait until Postgres has submissions: %v", err)
}

// err = waitUntilKeyspacesHasBlocksAndSubmissions(config)
// if err != nil {
// t.Fatalf("Failed to wait until Keyspaces has blocks and submissions: %v", err)
Expand Down
16 changes: 16 additions & 0 deletions src/integration_tests/postgres_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,22 @@ func StartPostgresContainerAndSetupSchema(config delegation_backend.PostgreSQLCo
return nil, fmt.Errorf("failed to connect to the database: %v", err)
}

// wait for the PostgreSQL to be ready
timeout := time.After(TIMEOUT_IN_S * time.Second)
tick := time.Tick(5 * time.Second)
ready := false
for !ready {
select {
case <-timeout:
return nil, fmt.Errorf("timeout reached while waiting for PostgreSQL to be ready")
case <-tick:
if err = db.Ping(); err == nil {
log.Println("PostgreSQL is ready")
ready = true
}
}
}

submissions_schema := `CREATE TABLE IF NOT EXISTS submissions (
id SERIAL PRIMARY KEY,
submitted_at_date DATE NOT NULL,
Expand Down
1 change: 0 additions & 1 deletion test/integration/topology/genesis_keys/node-c-key.json

This file was deleted.

5 changes: 0 additions & 5 deletions test/integration/topology/genesis_ledger.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,6 @@
"sk": "EKF5AkJQX1B6CgmmaXgsyw4tZFp1M27TRcJrzw67Mtnp7o45rEvv",
"balance": "300000"
},
{
"pk": "B62qoztWpP8cJbknVWCcW2u56Y44wLNqpsompb5dDGoG1o6nfeatTKX",
"sk": "EKF44LaJVQKTT1iuSJ3XifSFFtBndMb741kHPzVcYAtYEyJbZ11s",
"balance": "300000"
},
{
"pk": "B62qjwzawZqMmm27zwDg5xF8XtrH1TcQedsS3EVStct8wr1FcpRZFbm",
"sk": "EKFBPN3My4Y5GqaYj9pMYvtfzvrzuSQyvTEgxTZmCx4QZg7vJePB",
Expand Down
1 change: 0 additions & 1 deletion test/integration/topology/libp2p_keys/node-c.json

This file was deleted.

1 change: 0 additions & 1 deletion test/integration/topology/libp2p_keys/node-c.json.peerid

This file was deleted.

22 changes: 0 additions & 22 deletions test/integration/topology/topology.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,28 +66,6 @@
},
"libp2p_peerid": "12D3KooWGFjBhvkwj6jGTfhQxWMq4rTvw22vgbgnc2KPF4xhNhAv"
},
"node-c": {
"pk": "B62qoztWpP8cJbknVWCcW2u56Y44wLNqpsompb5dDGoG1o6nfeatTKX",
"sk": "EKF44LaJVQKTT1iuSJ3XifSFFtBndMb741kHPzVcYAtYEyJbZ11s",
"privkey_path": "../../test/integration/topology/block_producer_keys/node-c.json",
"role": "Block_producer",
"docker_image": "gcr.io/o1labs-192920/mina-daemon:2.0.0berkeley-rc1-1551e2f-bullseye-berkeley",
"git_build": null,
"libp2p_pass": "naughty blue worm",
"libp2p_keyfile": "../../test/integration/topology/libp2p_keys/node-c.json",
"libp2p_keypair": {
"box_primitive": "xsalsa20poly1305",
"pw_primitive": "argon2i",
"nonce": "7VnXm6xTdL6vtBBBopMf7wpmQFNnGrDnYkhMCun",
"pwsalt": "9Jisfm3uHzfX7fbnPhVBd3vCr98y",
"pwdiff": [
134217728,
6
],
"ciphertext": "8zWqpCUDvRoBhPnXdp7QRmwVPUivDJQh2rQ5NUkXMMHrNyhPQfUHtAjtNeCcwni9kBjP7ZGtdDckiPjYSL4YKApLmu7NtqD8RbnW9GDUQfrtmG28hMfqhJLcTTR3bX3YUZahppiDHoFF86x2vYHjXU9gzwspseJZcqS7sLH9bwKkoEoFj1LrC8SXoNhUseR3sJSJzmcfs3Hbcc6fjG3TsvcdPF7vY793p96L8FTJXePmmGjYAGnNhkQuFjKx9apA1nbuHcWqC3VowzyTd1UWU7N6cSutqTupswgyf"
},
"libp2p_peerid": "12D3KooWC3KLTGZygn5MYukqR1yVtSNR9mzM6BaaGKjoJTaaP4oi"
},
"uptime-service-backend": {
"role": "Uptime_service_backend",
"app_config_path": "../../test/integration/topology/uptime_service_config/app_config.json",
Expand Down

0 comments on commit 0aabeda

Please sign in to comment.