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

PM-1939 - Health endpoint #55

Merged
merged 9 commits into from
Sep 5, 2024
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
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
Loading