Refactors the API to run using dispatch instead of decorate

The API was never made an object since I couldn't find a way or working around the Flask decorators.

By using dispatch we can get around the issues in #14 and will be able to create better mocks for the API
This commit is contained in:
Sergi Delgado Segura
2019-12-17 17:04:57 +01:00
parent 6129ae9b25
commit 531523c534
3 changed files with 138 additions and 142 deletions

View File

@@ -14,13 +14,15 @@ from common.constants import HTTP_OK, HTTP_BAD_REQUEST, HTTP_SERVICE_UNAVAILABLE
# ToDo: #5-add-async-to-api # ToDo: #5-add-async-to-api
app = Flask(__name__) app = Flask(__name__)
logger = Logger("API") logger = Logger("API")
watcher = None
@app.route("/", methods=["POST"]) class API:
def add_appointment(): def __init__(self, watcher):
self.watcher = watcher
def add_appointment(self):
""" """
Add appointment endpoint, it is used as the main endpoint of the Watchtower. Main endpoint of the Watchtower.
The client sends requests (appointments) to this endpoint to request a job to the Watchtower. Requests must be json The client sends requests (appointments) to this endpoint to request a job to the Watchtower. Requests must be json
encoded and contain an ``appointment`` field and optionally a ``signature`` and ``public_key`` fields. encoded and contain an ``appointment`` field and optionally a ``signature`` and ``public_key`` fields.
@@ -48,7 +50,7 @@ def add_appointment():
response = None response = None
if type(appointment) == Appointment: if type(appointment) == Appointment:
appointment_added, signature = watcher.add_appointment(appointment) appointment_added, signature = self.watcher.add_appointment(appointment)
if appointment_added: if appointment_added:
rcode = HTTP_OK rcode = HTTP_OK
@@ -79,13 +81,11 @@ def add_appointment():
else: else:
return jsonify({"error": error}), rcode return jsonify({"error": error}), rcode
# FIXME: THE NEXT THREE API ENDPOINTS ARE FOR TESTING AND SHOULD BE REMOVED / PROPERLY MANAGED BEFORE PRODUCTION!
# FIXME: THE NEXT THREE API ENDPOINTS ARE FOR TESTING AND SHOULD BE REMOVED / PROPERLY MANAGED BEFORE PRODUCTION! # ToDo: #17-add-api-keys
# ToDo: #17-add-api-keys def get_appointment(self):
@app.route("/get_appointment", methods=["GET"])
def get_appointment():
""" """
Get appointment endpoint, it gives information about a given appointment state in the Watchtower. Gives information about a given appointment state in the Watchtower.
The information is requested by ``locator``. The information is requested by ``locator``.
@@ -108,11 +108,11 @@ def get_appointment():
response.append({"locator": locator, "status": "not_found"}) response.append({"locator": locator, "status": "not_found"})
return jsonify(response) return jsonify(response)
locator_map = watcher.db_manager.load_locator_map(locator) locator_map = self.watcher.db_manager.load_locator_map(locator)
if locator_map is not None: if locator_map is not None:
for uuid in locator_map: for uuid in locator_map:
appointment_data = watcher.db_manager.load_watcher_appointment(uuid) appointment_data = self.watcher.db_manager.load_watcher_appointment(uuid)
if appointment_data is not None and appointment_data["triggered"] is False: if appointment_data is not None and appointment_data["triggered"] is False:
# Triggered is an internal flag # Triggered is an internal flag
@@ -121,7 +121,7 @@ def get_appointment():
appointment_data["status"] = "being_watched" appointment_data["status"] = "being_watched"
response.append(appointment_data) response.append(appointment_data)
tracker_data = watcher.db_manager.load_responder_tracker(uuid) tracker_data = self.watcher.db_manager.load_responder_tracker(uuid)
if tracker_data is not None: if tracker_data is not None:
tracker_data["status"] = "dispute_responded" tracker_data["status"] = "dispute_responded"
@@ -134,11 +134,9 @@ def get_appointment():
return response return response
def get_all_appointments(self):
@app.route("/get_all_appointments", methods=["GET"])
def get_all_appointments():
""" """
Get all appointments endpoint, it gives information about all the appointments in the Watchtower. Gives information about all the appointments in the Watchtower.
This endpoint should only be accessible by the administrator. Requests are only allowed from localhost. This endpoint should only be accessible by the administrator. Requests are only allowed from localhost.
@@ -153,8 +151,8 @@ def get_all_appointments():
response = None response = None
if request.remote_addr in request.host or request.remote_addr == "127.0.0.1": if request.remote_addr in request.host or request.remote_addr == "127.0.0.1":
watcher_appointments = watcher.db_manager.load_watcher_appointments() watcher_appointments = self.watcher.db_manager.load_watcher_appointments()
responder_trackers = watcher.db_manager.load_responder_trackers() responder_trackers = self.watcher.db_manager.load_responder_trackers()
response = jsonify({"watcher_appointments": watcher_appointments, "responder_trackers": responder_trackers}) response = jsonify({"watcher_appointments": watcher_appointments, "responder_trackers": responder_trackers})
@@ -163,11 +161,10 @@ def get_all_appointments():
return response return response
@staticmethod
@app.route("/get_block_count", methods=["GET"]) def get_block_count():
def get_block_count():
""" """
Get block count endpoint, it provides the block height of the Watchtower. Provides the block height of the Watchtower.
This is a testing endpoint that (most likely) will be removed in production. Its purpose is to give information to This is a testing endpoint that (most likely) will be removed in production. Its purpose is to give information to
testers about the current block so they can define a dummy appointment without having to run a bitcoin node. testers about the current block so they can define a dummy appointment without having to run a bitcoin node.
@@ -179,21 +176,20 @@ def get_block_count():
return jsonify({"block_count": BlockProcessor.get_block_count()}) return jsonify({"block_count": BlockProcessor.get_block_count()})
def start(self):
def start_api(w):
""" """
This function starts the Flask server used to run the API. This function starts the Flask server used to run the API. Adds all the routes to the functions listed above.
Args:
w (:obj:`Watcher <pisa.watcher.Watcher>`): A ``Watcher`` object.
""" """
# FIXME: Pretty ugly but I haven't found a proper way to pass it to add_appointment routes = {
global watcher "/": (self.add_appointment, ["POST"]),
"/get_appointment": (self.get_appointment, ["GET"]),
"/get_all_appointments": (self.get_all_appointments, ["GET"]),
"/get_block_count": (self.get_block_count, ["GET"]),
}
# ToDo: #18-separate-api-from-watcher for url, params in routes.items():
watcher = w app.add_url_rule(url, view_func=params[0], methods=params[1])
# Setting Flask log to ERROR only so it does not mess with out logging. Also disabling flask initial messages # Setting Flask log to ERROR only so it does not mess with out logging. Also disabling flask initial messages
logging.getLogger("werkzeug").setLevel(logging.ERROR) logging.getLogger("werkzeug").setLevel(logging.ERROR)

View File

@@ -4,7 +4,7 @@ from signal import signal, SIGINT, SIGQUIT, SIGTERM
from pisa.conf import DB_PATH from pisa.conf import DB_PATH
from common.logger import Logger from common.logger import Logger
from pisa.api import start_api from pisa.api import API
from pisa.watcher import Watcher from pisa.watcher import Watcher
from pisa.builder import Builder from pisa.builder import Builder
from pisa.conf import BTC_NETWORK, PISA_SECRET_KEY from pisa.conf import BTC_NETWORK, PISA_SECRET_KEY
@@ -79,8 +79,8 @@ if __name__ == "__main__":
watcher.appointments, watcher.locator_uuid_map = Builder.build_appointments(watcher_appointments_data) watcher.appointments, watcher.locator_uuid_map = Builder.build_appointments(watcher_appointments_data)
watcher.block_queue = Builder.build_block_queue(missed_blocks_watcher) watcher.block_queue = Builder.build_block_queue(missed_blocks_watcher)
# Create an instance of the Watcher and fire the API # Fire the API
start_api(watcher) API(watcher).start()
except Exception as e: except Exception as e:
logger.error("An error occurred: {}. Shutting down".format(e)) logger.error("An error occurred: {}. Shutting down".format(e))

View File

@@ -5,7 +5,7 @@ from time import sleep
from threading import Thread from threading import Thread
from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives import serialization
from pisa.api import start_api from pisa.api import API
from pisa.watcher import Watcher from pisa.watcher import Watcher
from pisa.tools import bitcoin_cli from pisa.tools import bitcoin_cli
from pisa import HOST, PORT, c_logger from pisa import HOST, PORT, c_logger
@@ -40,7 +40,7 @@ def run_api(db_manager):
) )
watcher = Watcher(db_manager, sk_der) watcher = Watcher(db_manager, sk_der)
api_thread = Thread(target=start_api, args=[watcher]) api_thread = Thread(target=API(watcher).start)
api_thread.daemon = True api_thread.daemon = True
api_thread.start() api_thread.start()