feat_: status-backend health endpoint (#6201)

* feat_: status-backend health endpoint

* test_: health check

* test_: use health endpoint from python
This commit is contained in:
Igor Sirotin 2024-12-17 15:37:53 +00:00 committed by GitHub
parent 6a5623bac6
commit 309d17ae5b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 76 additions and 16 deletions

View File

@ -245,6 +245,12 @@ Access the exposed API with any HTTP client you prefer:
- [Python](https://pypi.org/project/requests/)
- [Go](https://pkg.go.dev/net/http)
## `status-backend` API
- `/health`
This is a basic health check endpoint. Response contains a single `version` property.
Returns HTTP code 200 if alive.
# 👌 Simple flows
In most cases to start testing you'll need some boilerplate. Below are the simple call flows for common cases.

View File

@ -0,0 +1,24 @@
package api
import (
"encoding/json"
"net/http"
"github.com/status-im/status-go/internal/version"
)
type HealthResponse struct {
Version string `json:"version,omitempty"`
}
func Health(w http.ResponseWriter, r *http.Request) {
response := HealthResponse{
Version: version.Version(),
}
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(response)
if err != nil {
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
return
}
}

View File

@ -15,6 +15,7 @@ import (
"github.com/pkg/errors"
"github.com/status-im/status-go/cmd/status-backend/server/api"
"github.com/status-im/status-go/signal"
)
@ -93,6 +94,7 @@ func (s *Server) Listen(address string) error {
}
s.mux = http.NewServeMux()
s.mux.HandleFunc("/health", api.Health)
s.mux.HandleFunc("/signals", s.signals)
s.server.Handler = s.mux

View File

@ -47,4 +47,21 @@ Functional tests for status-go
- Every test has two types of verifications:
- `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.
- `jsonschema.validate()` is used to check that the response contains expected data, including required fields and types. Schemas are stored in `/schemas/wallet_MethodName`
- 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
- 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
# Known issues
## Docker permission denied
When running tests with auto-creating status-backend containers, you might face this:
```shell
sock.connect(self.unix_socket)
PermissionError: [Errno 13] Permission denied
```
Please follow this fix: https://github.com/docker/compose/issues/10299#issuecomment-1438247730
If you're on MacOS and `/var/run/docker.sock` doesn't exist, you need to create a symlink to the docker socket:
```shell
sudo ln -s $HOME/.docker/run/docker.sock /var/run/docker.sock
```

View File

@ -14,6 +14,8 @@ from datetime import datetime
from conftest import option
from resources.constants import user_1, DEFAULT_DISPLAY_NAME, USER_DIR
NANOSECONDS_PER_SECOND = 1_000_000_000
class StatusBackend(RpcClient, SignalClient):
@ -29,6 +31,7 @@ class StatusBackend(RpcClient, SignalClient):
url = f"http://127.0.0.1:{host_port}"
option.status_backend_port_range.remove(host_port)
self.base_url = url
self.api_url = f"{url}/statusgo"
self.ws_url = f"{url}".replace("http", "ws")
self.rpc_url = f"{url}/statusgo/CallRPC"
@ -36,7 +39,7 @@ class StatusBackend(RpcClient, SignalClient):
RpcClient.__init__(self, self.rpc_url)
SignalClient.__init__(self, self.ws_url, await_signals)
self._health_check()
self.wait_for_healthy()
websocket_thread = threading.Thread(target=self._connect)
websocket_thread.daemon = True
@ -72,7 +75,6 @@ class StatusBackend(RpcClient, SignalClient):
}
},
}
if "FUNCTIONAL_TESTS_DOCKER_UID" in os.environ:
container_args["user"] = os.environ["FUNCTIONAL_TESTS_DOCKER_UID"]
@ -84,23 +86,29 @@ class StatusBackend(RpcClient, SignalClient):
option.status_backend_containers.append(container.id)
return container
def _health_check(self):
def wait_for_healthy(self, timeout=10):
start_time = time.time()
while True:
while time.time() - start_time <= timeout:
try:
self.api_valid_request(method="Fleets", data=[])
break
self.health(enable_logging=False)
logging.info(f"StatusBackend is healthy after {time.time() - start_time} seconds")
return
except Exception as e:
if time.time() - start_time > 20:
raise Exception(e)
time.sleep(1)
time.sleep(0.1)
raise TimeoutError(
f"StatusBackend was not healthy after {timeout} seconds")
def api_request(self, method, data, url=None):
def health(self, enable_logging=True):
return self.api_request("health", data=[], url=self.base_url, enable_logging=enable_logging)
def api_request(self, method, data, url=None, enable_logging=True):
url = url if url else self.api_url
url = f"{url}/{method}"
logging.info(f"Sending POST request to url {url} with data: {json.dumps(data, sort_keys=True, indent=4)}")
if enable_logging:
logging.info(f"Sending POST request to url {url} with data: {json.dumps(data, sort_keys=True, indent=4)}")
response = requests.post(url, json=data)
logging.info(f"Got response: {response.content}")
if enable_logging:
logging.info(f"Got response: {response.content}")
return response
def verify_is_valid_api_response(self, response):
@ -115,8 +123,8 @@ class StatusBackend(RpcClient, SignalClient):
except KeyError:
pass
def api_valid_request(self, method, data):
response = self.api_request(method, data)
def api_valid_request(self, method, data, url=None):
response = self.api_request(method, data, url)
self.verify_is_valid_api_response(response)
return response

View File

@ -23,6 +23,10 @@ class TestInitialiseApp:
backend_client.restore_account_and_login()
assert backend_client is not None
backend_client.verify_json_schema(
backend_client.wait_for_login(),
"signal_node_login",
)
backend_client.verify_json_schema(
backend_client.wait_for_signal(SignalType.MEDIASERVER_STARTED.value),
"signal_mediaserver_started",
@ -35,7 +39,6 @@ class TestInitialiseApp:
backend_client.wait_for_signal(SignalType.NODE_READY.value),
"signal_node_ready",
)
backend_client.verify_json_schema(backend_client.wait_for_login(), "signal_node_login")
@pytest.mark.rpc