Skip to content

Commit 309d17a

Browse files
authored
feat_: status-backend health endpoint (#6201)
* feat_: status-backend health endpoint * test_: health check * test_: use health endpoint from python
1 parent 6a5623b commit 309d17a

File tree

6 files changed

+76
-16
lines changed

6 files changed

+76
-16
lines changed

cmd/status-backend/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,12 @@ Access the exposed API with any HTTP client you prefer:
245245
- [Python](https://pypi.org/project/requests/)
246246
- [Go](https://pkg.go.dev/net/http)
247247
248+
## `status-backend` API
249+
250+
- `/health`
251+
This is a basic health check endpoint. Response contains a single `version` property.
252+
Returns HTTP code 200 if alive.
253+
248254
# 👌 Simple flows
249255
250256
In most cases to start testing you'll need some boilerplate. Below are the simple call flows for common cases.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package api
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
7+
"github.com/status-im/status-go/internal/version"
8+
)
9+
10+
type HealthResponse struct {
11+
Version string `json:"version,omitempty"`
12+
}
13+
14+
func Health(w http.ResponseWriter, r *http.Request) {
15+
response := HealthResponse{
16+
Version: version.Version(),
17+
}
18+
w.Header().Set("Content-Type", "application/json")
19+
err := json.NewEncoder(w).Encode(response)
20+
if err != nil {
21+
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
22+
return
23+
}
24+
}

cmd/status-backend/server/server.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
"github.com/pkg/errors"
1717

18+
"github.com/status-im/status-go/cmd/status-backend/server/api"
1819
"github.com/status-im/status-go/signal"
1920
)
2021

@@ -93,6 +94,7 @@ func (s *Server) Listen(address string) error {
9394
}
9495

9596
s.mux = http.NewServeMux()
97+
s.mux.HandleFunc("/health", api.Health)
9698
s.mux.HandleFunc("/signals", s.signals)
9799
s.server.Handler = s.mux
98100

tests-functional/README.MD

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,21 @@ Functional tests for status-go
4747
- Every test has two types of verifications:
4848
- `verify_is_valid_json_rpc_response()` checks for status code 200, non-empty response, JSON-RPC structure, presence of the `result` field, and expected ID.
4949
- `jsonschema.validate()` is used to check that the response contains expected data, including required fields and types. Schemas are stored in `/schemas/wallet_MethodName`
50-
- New schemas can be generated using `./tests-functional/utils/schema_builder.py` by passing a response to the `CustomSchemaBuilder(schema_name).create_schema(response.json())` method, should be used only on test creation phase, please search `how to create schema:` to see an example in a test
50+
- New schemas can be generated using `./tests-functional/utils/schema_builder.py` by passing a response to the `CustomSchemaBuilder(schema_name).create_schema(response.json())` method, should be used only on test creation phase, please search `how to create schema:` to see an example in a test
51+
52+
# Known issues
53+
54+
## Docker permission denied
55+
56+
When running tests with auto-creating status-backend containers, you might face this:
57+
```shell
58+
sock.connect(self.unix_socket)
59+
PermissionError: [Errno 13] Permission denied
60+
```
61+
62+
Please follow this fix: https://github.com/docker/compose/issues/10299#issuecomment-1438247730
63+
64+
If you're on MacOS and `/var/run/docker.sock` doesn't exist, you need to create a symlink to the docker socket:
65+
```shell
66+
sudo ln -s $HOME/.docker/run/docker.sock /var/run/docker.sock
67+
```

tests-functional/clients/status_backend.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
from conftest import option
1515
from resources.constants import user_1, DEFAULT_DISPLAY_NAME, USER_DIR
1616

17+
NANOSECONDS_PER_SECOND = 1_000_000_000
18+
1719

1820
class StatusBackend(RpcClient, SignalClient):
1921

@@ -29,14 +31,15 @@ def __init__(self, await_signals=[]):
2931
url = f"http://127.0.0.1:{host_port}"
3032
option.status_backend_port_range.remove(host_port)
3133

34+
self.base_url = url
3235
self.api_url = f"{url}/statusgo"
3336
self.ws_url = f"{url}".replace("http", "ws")
3437
self.rpc_url = f"{url}/statusgo/CallRPC"
3538

3639
RpcClient.__init__(self, self.rpc_url)
3740
SignalClient.__init__(self, self.ws_url, await_signals)
3841

39-
self._health_check()
42+
self.wait_for_healthy()
4043

4144
websocket_thread = threading.Thread(target=self._connect)
4245
websocket_thread.daemon = True
@@ -72,7 +75,6 @@ def _start_container(self, host_port):
7275
}
7376
},
7477
}
75-
7678
if "FUNCTIONAL_TESTS_DOCKER_UID" in os.environ:
7779
container_args["user"] = os.environ["FUNCTIONAL_TESTS_DOCKER_UID"]
7880

@@ -84,23 +86,29 @@ def _start_container(self, host_port):
8486
option.status_backend_containers.append(container.id)
8587
return container
8688

87-
def _health_check(self):
89+
def wait_for_healthy(self, timeout=10):
8890
start_time = time.time()
89-
while True:
91+
while time.time() - start_time <= timeout:
9092
try:
91-
self.api_valid_request(method="Fleets", data=[])
92-
break
93+
self.health(enable_logging=False)
94+
logging.info(f"StatusBackend is healthy after {time.time() - start_time} seconds")
95+
return
9396
except Exception as e:
94-
if time.time() - start_time > 20:
95-
raise Exception(e)
96-
time.sleep(1)
97+
time.sleep(0.1)
98+
raise TimeoutError(
99+
f"StatusBackend was not healthy after {timeout} seconds")
100+
101+
def health(self, enable_logging=True):
102+
return self.api_request("health", data=[], url=self.base_url, enable_logging=enable_logging)
97103

98-
def api_request(self, method, data, url=None):
104+
def api_request(self, method, data, url=None, enable_logging=True):
99105
url = url if url else self.api_url
100106
url = f"{url}/{method}"
101-
logging.info(f"Sending POST request to url {url} with data: {json.dumps(data, sort_keys=True, indent=4)}")
107+
if enable_logging:
108+
logging.info(f"Sending POST request to url {url} with data: {json.dumps(data, sort_keys=True, indent=4)}")
102109
response = requests.post(url, json=data)
103-
logging.info(f"Got response: {response.content}")
110+
if enable_logging:
111+
logging.info(f"Got response: {response.content}")
104112
return response
105113

106114
def verify_is_valid_api_response(self, response):
@@ -115,8 +123,8 @@ def verify_is_valid_api_response(self, response):
115123
except KeyError:
116124
pass
117125

118-
def api_valid_request(self, method, data):
119-
response = self.api_request(method, data)
126+
def api_valid_request(self, method, data, url=None):
127+
response = self.api_request(method, data, url)
120128
self.verify_is_valid_api_response(response)
121129
return response
122130

tests-functional/tests/test_init_status_app.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ def test_init_app(self):
2323
backend_client.restore_account_and_login()
2424

2525
assert backend_client is not None
26+
backend_client.verify_json_schema(
27+
backend_client.wait_for_login(),
28+
"signal_node_login",
29+
)
2630
backend_client.verify_json_schema(
2731
backend_client.wait_for_signal(SignalType.MEDIASERVER_STARTED.value),
2832
"signal_mediaserver_started",
@@ -35,7 +39,6 @@ def test_init_app(self):
3539
backend_client.wait_for_signal(SignalType.NODE_READY.value),
3640
"signal_node_ready",
3741
)
38-
backend_client.verify_json_schema(backend_client.wait_for_login(), "signal_node_login")
3942

4043

4144
@pytest.mark.rpc

0 commit comments

Comments
 (0)