Skip to content
This repository has been archived by the owner on Sep 26, 2022. It is now read-only.

Adds event to signal teosd is ready #276

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions common/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

# HTTP
HTTP_OK = 200
HTTP_EMPTY = 204
HTTP_BAD_REQUEST = 400
HTTP_NOT_FOUND = 404
HTTP_SERVICE_UNAVAILABLE = 503
Expand Down
8 changes: 8 additions & 0 deletions contrib/client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,19 @@ Refer to [INSTALL.md](INSTALL.md)

The client has four commands:

- `ping`: pings the tower to check if it's online.
- `register`: registers your user with the tower.
- `add_appointment`: sends a json formatted appointment to the tower.
- `get_appointment`: gets json formatted data about an appointment from the tower.
- `help`: shows a list of commands or help for a specific command.

### ping
This command is used to check the status of the tower.

#### Usage

teos-client ping

### register
This commands serves as registration. It sends your public key to the tower to create a subscription (free at the moment) and returns a number of available appointment slots in the tower. Topping up the subscription can be done by simply sending a register message again.

Expand Down
12 changes: 12 additions & 0 deletions contrib/client/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ def show_usage():
"USAGE: "
"\n\tteos-client [global options] command [command options] [arguments]"
"\n\nCOMMANDS:"
"\n\tping \t\t\tPings the tower to check if it's online."
"\n\tregister \t\tRegisters your user public key with the tower."
"\n\tadd_appointment \tRegisters a json formatted appointment with the tower."
"\n\tget_appointment \tGets json formatted data about an appointment from the tower."
Expand All @@ -16,6 +17,17 @@ def show_usage():
)


def help_ping():
return (
"NAME:"
"\n\n\tping"
"\n\nUSAGE:"
"\n\n\tteos-client ping"
"\n\nDESCRIPTION:"
"\n\n\tPings the tower to check if it is online."
)


def help_register():
return (
"NAME:"
Expand Down
41 changes: 38 additions & 3 deletions contrib/client/teos_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,43 @@
help_get_appointment,
help_get_subscription_info,
help_register,
help_ping,
)

logging.basicConfig(level=logging.INFO, format="%(message)s")

logger = logging.getLogger()


def ping(teos_url):
"""
Pings the tower to check if it is online.

Args:
teos_url (:obj:`str`): the teos base url.

Raises:
:obj:`TowerResponseError`: if the tower responded with an error, or the response was invalid.
:obj:`ConnectionError`: if the client cannot connect to the tower.
"""

ping_endpoint = "{}/ping".format(teos_url)

logger.info(f"Pinging the Eye of Satoshi at {teos_url}")

try:
response = requests.get(ping_endpoint)

if response.status_code == constants.HTTP_EMPTY:
logger.info(f"The Eye of Satoshi is alive")
else:
raise TowerResponseError(
"The server returned an error", status_code=response.status_code, reason=response.reason, data=response,
)
except ConnectionError:
raise ConnectionError(f"The Eye of Satoshi is down")


def register(user_id, teos_id, teos_url):
"""
Registers the user to the tower.
Expand All @@ -51,8 +81,7 @@ def register(user_id, teos_id, teos_url):
Raises:
:obj:`InvalidParameter`: if `user_id` is invalid.
:obj:`ConnectionError`: if the client cannot connect to the tower.
:obj:`TowerResponseError`: if the tower responded with an error, or the
response was invalid.
:obj:`TowerResponseError`: if the tower responded with an error, or the response was invalid.
"""

if not is_compressed_pk(user_id):
Expand Down Expand Up @@ -460,6 +489,9 @@ def main(command, args, command_line_conf):
Cryptographer.save_key_file(user_sk.to_der(), "user_sk", config.get("DATA_DIR"))
user_id = Cryptographer.get_compressed_pk(user_sk.public_key)

if command == "ping":
ping(teos_url)

if command == "register":
if not args:
raise InvalidParameter("Cannot register. No tower id was given")
Expand Down Expand Up @@ -515,6 +547,9 @@ def main(command, args, command_line_conf):
if args:
command = args.pop(0)

if command == "ping":
sys.exit(help_ping())

if command == "register":
sys.exit(help_register())

Expand All @@ -541,7 +576,7 @@ def main(command, args, command_line_conf):

def run():
command_line_conf = {}
commands = ["register", "add_appointment", "get_appointment", "get_subscription_info", "help"]
commands = ["ping", "register", "add_appointment", "get_appointment", "get_subscription_info", "help"]

try:
opts, args = getopt(argv[1:], "h", ["apiconnect=", "apiport=", "help"])
Expand Down
17 changes: 17 additions & 0 deletions contrib/client/test/test_teos_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

teos_url = "http://{}:{}".format(config.get("API_CONNECT"), config.get("API_PORT"))
add_appointment_endpoint = "{}/add_appointment".format(teos_url)
ping_endpoint = "{}/ping".format(teos_url)
register_endpoint = "{}/register".format(teos_url)
get_appointment_endpoint = "{}/get_appointment".format(teos_url)
get_all_appointments_endpoint = "{}/get_all_appointments".format(teos_url)
Expand Down Expand Up @@ -83,6 +84,22 @@ def post_response():
}


@responses.activate
def test_ping():
# Simulate a ping response with the tower offline
with pytest.raises(ConnectionError):
teos_client.ping(teos_url)

# Simulate a ping response with the tower online
responses.add(responses.GET, ping_endpoint, status=204)
teos_client.ping(teos_url)

# Simulate a ping response with the tower erroring
with pytest.raises(TowerResponseError):
responses.replace(responses.GET, ping_endpoint, status=404)
teos_client.ping(teos_url)


@responses.activate
def test_register():
# Simulate a register response
Expand Down
39 changes: 36 additions & 3 deletions teos/api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import grpc
import requests
from time import sleep
from google.protobuf import json_format
from waitress import serve as wsgi_serve
from flask import Flask, request, jsonify
Expand All @@ -7,7 +9,7 @@
from common.tools import intify
from common.exceptions import InvalidParameter
from common.appointment import AppointmentStatus
from common.constants import HTTP_OK, HTTP_BAD_REQUEST, HTTP_SERVICE_UNAVAILABLE, HTTP_NOT_FOUND
from common.constants import HTTP_OK, HTTP_EMPTY, HTTP_BAD_REQUEST, HTTP_SERVICE_UNAVAILABLE, HTTP_NOT_FOUND

from teos.logger import setup_logging, get_logger
from teos.inspector import Inspector, InspectionFailed
Expand Down Expand Up @@ -63,15 +65,15 @@ def serve(internal_api_endpoint, endpoint, logging_port, min_to_self_delay, auto
"""
Starts the API.

This method can be handled either form an external WSGI (like gunicorn) or by the Flask development server.
This method can be handled either form an external WSGI (like gunicorn) or by the Waitress.

Args:
internal_api_endpoint (:obj:`str`): endpoint where the internal api is running (``host:port``).
endpoint (:obj:`str`): endpoint where the http api will be running (``host:port``).
logging_port (:obj:`int`): the port where the logging server can be reached (localhost:logging_port)
min_to_self_delay (:obj:`str`): the minimum to_self_delay accepted by the :obj:`Inspector`.
auto_run (:obj:`bool`): whether the server should be started by this process. False if run with an external
WSGI. True is run by Flask.
WSGI. True is run by Waitress.

Returns:
The application object needed by the WSGI server to run if ``auto_run`` is False, :obj:`None` otherwise.
Expand All @@ -91,6 +93,23 @@ def serve(internal_api_endpoint, endpoint, logging_port, min_to_self_delay, auto
return api.app


def wait_until_ready(api_endpoint):
"""
Waits until the API is ready by polling the info endpoint until an HTTP_OK is received.

Args:
api_endpoint: endpoint where the http api will be running (``host:port``).
"""

ping_endpoint = f"{api_endpoint}/ping"
if not api_endpoint.startswith("http"):
ping_endpoint = f"http://{ping_endpoint}"

# Wait for the API
while requests.get(ping_endpoint).status_code != HTTP_EMPTY:
sleep(1)


class API:
"""
The :class:`API` is in charge of the interface between the user and the tower. It handles and serves user requests.
Expand Down Expand Up @@ -118,6 +137,7 @@ def __init__(self, inspector, internal_api_endpoint):

# Adds all the routes to the functions listed above.
routes = {
"/ping": (self.ping, ["GET"]),
"/register": (self.register, ["POST"]),
"/add_appointment": (self.add_appointment, ["POST"]),
"/get_appointment": (self.get_appointment, ["POST"]),
Expand All @@ -127,6 +147,19 @@ def __init__(self, inspector, internal_api_endpoint):
for url, params in routes.items():
self.app.add_url_rule(url, view_func=params[0], methods=params[1])

def ping(self):
"""
Ping endpoint. Serves the purpose of checking the status of the tower.

Returns:
:obj:`tuple`: A tuple containing the response (:obj:`str`) and response code (:obj:`int`). Currently the
response is empty.
"""

remote_addr = get_remote_addr()
self.logger.info("Received info request", from_addr="{}".format(remote_addr))
return "", HTTP_EMPTY

def register(self):
"""
Registers a user by creating a subscription.
Expand Down
5 changes: 4 additions & 1 deletion teos/rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import functools
from concurrent import futures
from signal import signal, SIGINT
from multiprocessing import Event

from teos.tools import ignore_signal
from teos.logger import setup_logging, get_logger
Expand Down Expand Up @@ -34,6 +35,7 @@ def __init__(self, rpc_bind, rpc_port, internal_api_endpoint):
self.endpoint = f"{rpc_bind}:{rpc_port}"
self.rpc_server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
self.rpc_server.add_insecure_port(self.endpoint)
self.ready = Event()
add_TowerServicesServicer_to_server(_RPC(internal_api_endpoint, self.logger), self.rpc_server)

def teardown(self):
Expand Down Expand Up @@ -99,7 +101,7 @@ def stop(self, request, context):
return self.stub.stop(request)


def serve(rpc_bind, rpc_port, internal_api_endpoint, logging_port, stop_event):
def serve(rpc_bind, rpc_port, internal_api_endpoint, logging_port, rpc_ready, stop_event):
"""
Serves the external RPC API at the given endpoint and connects it to the internal api.

Expand All @@ -121,6 +123,7 @@ def serve(rpc_bind, rpc_port, internal_api_endpoint, logging_port, stop_event):
rpc.rpc_server.start()

rpc.logger.info(f"Initialized. Serving at {rpc.endpoint}")
rpc_ready.set()

stop_event.wait()

Expand Down
Loading