diff --git a/Makefile b/Makefile index 8cd2f4c..9c8312d 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,20 @@ .PHONY: push-all + +VERSION=0.2.2 +IMAGE_NAME=kentbull/vlei + push-all: - @docker push gleif/vlei --all-tags + @docker push $(IMAGE_NAME) --all-tags .PHONY: build-vlei -build-vlei: - @docker buildx build --load --platform=linux/amd64 -f container/Dockerfile --tag gleif/vlei:latest --tag gleif/vlei:0.2.1 . +build: + @docker buildx build --load \ + --platform=linux/amd64,linux/arm64 \ + -f container/Dockerfile \ + --tag $(IMAGE_NAME):latest \ + --tag $(IMAGE_NAME):$(VERSION) \ + . + +publish: + @docker push $(IMAGE_NAME):latest + @docker push $(IMAGE_NAME):$(VERSION) diff --git a/setup.py b/setup.py index a13e32f..c07ace1 100644 --- a/setup.py +++ b/setup.py @@ -88,6 +88,7 @@ tests_require=[ 'coverage>=7.4.4', 'pytest>=8.1.1', +'requests==2.32.3' ], setup_requires=[ ], diff --git a/src/vlei/app/serving.py b/src/vlei/app/serving.py index 2d807fa..41f81f7 100644 --- a/src/vlei/app/serving.py +++ b/src/vlei/app/serving.py @@ -5,9 +5,9 @@ """ import os from pathlib import Path + import falcon from hio.core import http - from keri import help from vlei.app import caching diff --git a/src/vlei/app/shutdown.py b/src/vlei/app/shutdown.py new file mode 100644 index 0000000..db269a8 --- /dev/null +++ b/src/vlei/app/shutdown.py @@ -0,0 +1,62 @@ +import signal + +from hio.base import doing, Doist +from keri import help + +logger = help.ogler.getLogger() + +class GracefulShutdownDoer(doing.Doer): + """ + Shuts all Agency agents down before exiting the Doist loop, performing a graceful shutdown. + Sets up signal handlers in the Doer.enter lifecycle method and exits the Doist scheduler loop in Doer.exit + Checks for the signals in the Doer.recur lifecycle method. + """ + def __init__(self, doist, **kwa): + """ + Parameters: + doist (Doist): The Doist running this Doer + kwa (dict): Additional keyword arguments for Doer initialization + """ + self.doist: Doist = doist + self.shutdown_received = False + + super().__init__(**kwa) + + def handle_sigterm(self, signum, frame): + """Handler function for SIGTERM""" + logger.info(f"Received SIGTERM, initiating graceful shutdown.") + self.shutdown_received = True + + def handle_sigint(self, signum, frame): + """Handler function for SIGINT""" + logger.info(f"Received SIGINT, initiating graceful shutdown.") + self.shutdown_received = True + + def enter(self): + """ + Sets up signal handlers. + Lifecycle method called once when the Doist running this Doer enters the context for this Doer. + """ + # Register signal handler + signal.signal(signal.SIGTERM, self.handle_sigterm) + signal.signal(signal.SIGINT, self.handle_sigint) + logger.info("Registered signal handlers for SIGTERM and SIGINT") + + def recur(self, tock=0.0): + """Generator coroutine checking once per tock for shutdown flag""" + # Checks once per tock if the shutdown flag has been set and if so initiates the shutdown process + logger.info("Recurring graceful shutdown doer") + while not self.shutdown_received: + yield tock # will iterate forever in here until shutdown flag set + + # Once shutdown_flag is set, exit the Doist loop + return True # Returns a "done" status + # Causes the Doist scheduler to call .exit() lifecycle method below, killing the doist loop + + def exit(self): + """ + Exits the Doist loop. + Lifecycle method called once when the Doist running this Doer exits the context for this Doer. + """ + logger.info(f"Shutting down main Doist loop") + self.doist.exit() \ No newline at end of file diff --git a/src/vlei/server.py b/src/vlei/server.py index 0aafc7b..61645ab 100644 --- a/src/vlei/server.py +++ b/src/vlei/server.py @@ -1,19 +1,21 @@ # -*- encoding: utf-8 -*- """ -server module +vlei.server module +Contains the Doist and Doers comprising the vLEI server. """ import argparse import logging -import signal +from dataclasses import dataclass +from typing import List import falcon -from hio.base import doing +from hio.base import doing, Doer from hio.core import http, tcp - from keri import help from vlei.app import serving +from vlei.app.shutdown import GracefulShutdownDoer parser = argparse.ArgumentParser(description="Runs vLEI schema server") parser.add_argument('-p', '--http', @@ -39,9 +41,26 @@ parser.add_argument("--cafilepath", action="store", required=False, default=None, help="TLS server CA certificate chain") + logger = help.ogler.getLogger() +@dataclass +class VLEIConfig: + # HTTP port to listen on + http: int = 7723 + # ACDC schema directory + schemaDir: str = None + # ACDC material directory + credDir: str = None + # Well known OOBI directory + oobiDir: str = None + # TLS key material + keypath: str = None + certpath: str = None + cafilepath: str = None + + def createHttpServer(port, app, keypath=None, certpath=None, cafilepath=None): """ Create an HTTP or HTTPS server depending on whether TLS key material is present @@ -66,44 +85,57 @@ def createHttpServer(port, app, keypath=None, certpath=None, cafilepath=None): server = http.Server(port=port, app=app) return server - -def launch(args): - logger.setLevel(logging.INFO) +def setupVLEIDoers(config: VLEIConfig): + """Set up the doers that will be run by the Doist scheduler.""" app = falcon.App() - port = int(args.http) - keypath = args.keypath - certpath = args.certpath - cafilepath = args.cafilepath + port = int(config.http) + keypath = config.keypath + certpath = config.certpath + cafilepath = config.cafilepath if keypath is not None and certpath is not None and cafilepath is not None: logger.info(f"vLEI-server starting on port {port} with TLS enabled") else: logger.info(f"vLEI-server starting on port {port} with TLS disabled") - server = createHttpServer(port=int(args.http), app=app, - keypath=args.keypath, certpath=args.certpath, - cafilepath=args.cafilepath) + server = createHttpServer(port=int(config.http), app=app, + keypath=config.keypath, certpath=config.certpath, + cafilepath=config.cafilepath) if not server.reopen(): - raise RuntimeError(f"cannot create http server on port {int(args.http)}") + raise RuntimeError(f"cannot create http server on port {int(config.http)}") httpServerDoer = http.ServerDoer(server=server) - serving.loadEnds(app, schemaDir=args.schemaDir, credDir=args.credDir, oobiDir=args.oobiDir) + serving.loadEnds(app, schemaDir=config.schemaDir, credDir=config.credDir, oobiDir=config.oobiDir) doers = [httpServerDoer] + return doers - # Shutdown hook - def shutdownHandler(sig, frame): - logger.info("Received signal %s", signal.strsignal(sig)) - doist.exit() - signal.signal(signal.SIGTERM, shutdownHandler) - +def vLEIDoist(doers: List[Doer]): + """Creates a Doist that will run the vLEI server.""" tock = 0.03125 doist = doing.Doist(limit=0.0, tock=tock, real=True) - doist.do(doers=doers) # Enters the doist loop until shutdown + doers.append(GracefulShutdownDoer(doist=doist)) + doist.doers = doers + return doist + +def launch(config: VLEIConfig): + """Launches the vLEI server by calling Doist.do() on the Doers that make up the server.""" + logger.setLevel(logging.INFO) + doist = vLEIDoist(setupVLEIDoers(config)) + doist.do() # Enters the doist loop until shutdown logger.info("vLEI-server stopped") + def main(): args = parser.parse_args() - launch(args) + launch(VLEIConfig( + http=args.http, + schemaDir=args.schemaDir, + credDir=args.credDir, + oobiDir=args.oobiDir, + keypath=args.keypath, + certpath=args.certpath, + cafilepath=args.cafilepath + )) if __name__ == "__main__":