Skip to content

Commit ca7a69f

Browse files
authored
Merge pull request #11 from osancus/dev
migration to aries 0.5.3
2 parents 7863581 + d74f037 commit ca7a69f

62 files changed

Lines changed: 1673 additions & 1087 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGE.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#Files and packages that were modified/added to mold aca-py 0.5.3 to aca-ag 0.5.3
2+
3+
- admin.server.py
4+
- config.ledger.py
5+
- core.conductor.py
6+
- transport.pack_format.py
7+
- protocols.connections.v1_0.manager.py
8+
- agency (package added)

CHANGELOG.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1-
# 0.5.2-agency
1+
# 0.5.3-AG
2+
3+
## July 23, 2020
4+
5+
- Compatibility with aca-py 0.5.3, [ACA-PY Changelog](https://github.com/hyperledger/aries-cloudagent-python/blob/0.5.3/CHANGELOG.md).
6+
7+
# 0.5.2-AG
28

39
## July 6, 2020
410

511
- Enable aca-py to create and manage multiple wallets.
6-
- Send / Receive connections, credentials and proofs for separate wallets.
12+
- Send / Receive connections, credentials and proofs for separate wallets on demand.
713

814
# 0.5.2
915

aries_cloudagency/admin/server.py

Lines changed: 125 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from ..storage.indy import IndyStorage
3737
from ..config.ledger import ledger_config
3838

39+
3940
LOGGER = logging.getLogger(__name__)
4041

4142

@@ -51,11 +52,23 @@ class AdminStatusSchema(Schema):
5152
"""Schema for the status endpoint."""
5253

5354

55+
class AdminStatusLivelinessSchema(Schema):
56+
"""Schema for the liveliness endpoint."""
57+
58+
alive = fields.Boolean(description="Liveliness status", example=True)
59+
60+
61+
class AdminStatusReadinessSchema(Schema):
62+
"""Schema for the liveliness endpoint."""
63+
64+
ready = fields.Boolean(description="Readiness status", example=True)
65+
66+
5467
class AdminResponder(BaseResponder):
5568
"""Handle outgoing messages from message handlers."""
5669

5770
def __init__(
58-
self, context: InjectionContext, send: Coroutine, webhook: Coroutine, **kwargs
71+
self, context: InjectionContext, send: Coroutine, webhook: Coroutine, **kwargs,
5972
):
6073
"""
6174
Initialize an instance of `AdminResponder`.
@@ -93,10 +106,10 @@ class WebhookTarget:
93106
"""Class for managing webhook target information."""
94107

95108
def __init__(
96-
self,
97-
endpoint: str,
98-
topic_filter: Sequence[str] = None,
99-
max_attempts: int = None,
109+
self,
110+
endpoint: str,
111+
topic_filter: Sequence[str] = None,
112+
max_attempts: int = None,
100113
):
101114
"""Initialize the webhook target."""
102115
self.endpoint = endpoint
@@ -118,18 +131,45 @@ def topic_filter(self, val: Sequence[str]):
118131
self._topic_filter = filter
119132

120133

134+
@web.middleware
135+
async def ready_middleware(request: web.BaseRequest, handler: Coroutine):
136+
"""Only continue if application is ready to take work."""
137+
138+
if str(request.rel_url).rstrip("/") in (
139+
"/status/live",
140+
"/status/ready",
141+
) or request.app._state.get("ready"):
142+
return await handler(request)
143+
144+
raise web.HTTPServiceUnavailable(reason="Shutdown in progress")
145+
146+
147+
@web.middleware
148+
async def debug_middleware(request: web.BaseRequest, handler: Coroutine):
149+
"""Show request detail in debug log."""
150+
151+
if LOGGER.isEnabledFor(logging.DEBUG):
152+
LOGGER.debug(f"Incoming request: {request.method} {request.path_qs}")
153+
LOGGER.debug(f"Match info: {request.match_info}")
154+
body = await request.text()
155+
LOGGER.debug(f"Body: {body}")
156+
157+
return await handler(request)
158+
159+
121160
class AdminServer(BaseAdminServer):
122161
"""Admin HTTP server class."""
123162

124163
def __init__(
125-
self,
126-
host: str,
127-
port: int,
128-
context: InjectionContext,
129-
outbound_message_router: Coroutine,
130-
webhook_router: Callable,
131-
task_queue: TaskQueue = None,
132-
conductor_stats: Coroutine = None,
164+
self,
165+
host: str,
166+
port: int,
167+
context: InjectionContext,
168+
outbound_message_router: Coroutine,
169+
webhook_router: Callable,
170+
conductor_stop: Coroutine,
171+
task_queue: TaskQueue = None,
172+
conductor_stats: Coroutine = None,
133173
):
134174
"""
135175
Initialize an AdminServer instance.
@@ -140,6 +180,7 @@ def __init__(
140180
context: The application context instance
141181
outbound_message_router: Coroutine for delivering outbound messages
142182
webhook_router: Callable for delivering webhooks
183+
conductor_stop: Conductor (graceful) stop for shutdown API call
143184
task_queue: An optional task queue for handlers
144185
"""
145186
self.app = None
@@ -149,6 +190,7 @@ def __init__(
149190
)
150191
self.host = host
151192
self.port = port
193+
self.conductor_stop = conductor_stop
152194
self.conductor_stats = conductor_stats
153195
self.loaded_modules = []
154196
self.task_queue = task_queue
@@ -159,14 +201,14 @@ def __init__(
159201

160202
self.context = context.start_scope("admin")
161203
self.responder = AdminResponder(
162-
self.context, outbound_message_router, self.send_webhook
204+
self.context, outbound_message_router, self.send_webhook,
163205
)
164206
self.context.injector.bind_instance(BaseResponder, self.responder)
165207

166208
async def make_application(self) -> web.Application:
167209
"""Get the aiohttp application instance."""
168210

169-
middlewares = [validation_middleware]
211+
middlewares = [ready_middleware, debug_middleware, validation_middleware]
170212

171213
# admin-token and admin-token are mutually exclusive and required.
172214
# This should be enforced during parameter parsing but to be sure,
@@ -225,22 +267,17 @@ async def collect_stats(request, handler):
225267
@web.middleware
226268
async def agency_middleware(request, handler):
227269
omit_list = ["/create_wallet"]
228-
229270
if request.rel_url.path not in omit_list:
230271
wallet_key = request.headers.get("wallet-key")
231272
wallet_name = request.headers.get("wallet-name")
232-
233273
self.context.settings.set_value("wallet.key", wallet_key)
234274
self.context.settings.set_value("wallet.name", wallet_name)
235-
236275
self.context.injector.clear_binding(BaseWallet)
237276
self.context.injector.clear_binding(BaseStorage)
238-
239277
wallet_instance: BaseWallet = await agency_wallet.get(wallet_name, wallet_key)
240278
if wallet_instance is None:
241279
raise web.HTTPUnauthorized()
242280
self.context.injector.bind_instance(BaseWallet, wallet_instance)
243-
244281
storage = IndyStorage(wallet_instance)
245282
self.context.injector.bind_instance(BaseStorage, storage)
246283
await wallet_config(self.context)
@@ -255,6 +292,9 @@ async def agency_middleware(request, handler):
255292
web.get("/plugins", self.plugins_handler, allow_head=False),
256293
web.get("/status", self.status_handler, allow_head=False),
257294
web.post("/status/reset", self.status_reset_handler),
295+
web.get("/status/live", self.liveliness_handler, allow_head=False),
296+
web.get("/status/ready", self.readiness_handler, allow_head=False),
297+
web.get("/shutdown", self.shutdown_handler, allow_head=False),
258298
web.get("/ws", self.websocket_handler, allow_head=False),
259299
web.post('/create_wallet', agency_wallet.create),
260300
]
@@ -307,10 +347,20 @@ async def start(self) -> None:
307347
if plugin_registry:
308348
plugin_registry.post_process_routes(self.app)
309349

350+
# order tags alphabetically, parameters deterministically and pythonically
351+
swagger_dict = self.app._state["swagger_dict"]
352+
swagger_dict.get("tags", []).sort(key=lambda t: t["name"])
353+
for path in swagger_dict["paths"].values():
354+
for method_spec in path.values():
355+
method_spec["parameters"].sort(
356+
key=lambda p: (p["in"], not p["required"], p["name"])
357+
)
358+
310359
self.site = web.TCPSite(runner, host=self.host, port=self.port)
311360

312361
try:
313362
await self.site.start()
363+
self.app._state["ready"] = True
314364
except OSError:
315365
raise AdminSetupError(
316366
"Unable to start webserver with host "
@@ -319,6 +369,7 @@ async def start(self) -> None:
319369

320370
async def stop(self) -> None:
321371
"""Stop the webserver."""
372+
self.app._state["ready"] = False # in case call does not come through OpenAPI
322373
for queue in self.websocket_queues.values():
323374
queue.stop()
324375
if self.site:
@@ -367,6 +418,7 @@ async def status_handler(self, request: web.BaseRequest):
367418
368419
"""
369420
status = {"version": __version__}
421+
status["label"] = self.context.settings.get("default_label")
370422
collector: Collector = await self.context.inject(Collector, required=False)
371423
if collector:
372424
status["timing"] = collector.results
@@ -396,6 +448,54 @@ async def redirect_handler(self, request: web.BaseRequest):
396448
"""Perform redirect to documentation."""
397449
raise web.HTTPFound("/api/doc")
398450

451+
@docs(tags=["server"], summary="Liveliness check")
452+
@response_schema(AdminStatusLivelinessSchema(), 200)
453+
async def liveliness_handler(self, request: web.BaseRequest):
454+
"""
455+
Request handler for liveliness check.
456+
457+
Args:
458+
request: aiohttp request object
459+
460+
Returns:
461+
The web response, always indicating True
462+
463+
"""
464+
return web.json_response({"alive": True})
465+
466+
@docs(tags=["server"], summary="Readiness check")
467+
@response_schema(AdminStatusReadinessSchema(), 200)
468+
async def readiness_handler(self, request: web.BaseRequest):
469+
"""
470+
Request handler for liveliness check.
471+
472+
Args:
473+
request: aiohttp request object
474+
475+
Returns:
476+
The web response, indicating readiness for further calls
477+
478+
"""
479+
return web.json_response({"ready": self.app._state["ready"]})
480+
481+
@docs(tags=["server"], summary="Shut down server")
482+
async def shutdown_handler(self, request: web.BaseRequest):
483+
"""
484+
Request handler for server shutdown.
485+
486+
Args:
487+
request: aiohttp request object
488+
489+
Returns:
490+
The web response (empty production)
491+
492+
"""
493+
self.app._state["ready"] = False
494+
loop = asyncio.get_event_loop()
495+
asyncio.ensure_future(self.conductor_stop(), loop=loop)
496+
497+
return web.json_response({})
498+
399499
async def websocket_handler(self, request):
400500
"""Send notifications to admin client over websocket."""
401501

@@ -477,6 +577,7 @@ async def websocket_handler(self, request):
477577
if msg:
478578
await ws.send_json(msg)
479579
send = loop.create_task(queue.dequeue(timeout=5.0))
580+
480581
except asyncio.CancelledError:
481582
closed = True
482583

@@ -491,10 +592,10 @@ async def websocket_handler(self, request):
491592
return ws
492593

493594
def add_webhook_target(
494-
self,
495-
target_url: str,
496-
topic_filter: Sequence[str] = None,
497-
max_attempts: int = None,
595+
self,
596+
target_url: str,
597+
topic_filter: Sequence[str] = None,
598+
max_attempts: int = None,
498599
):
499600
"""Add a webhook target."""
500601
self.webhook_targets[target_url] = WebhookTarget(

0 commit comments

Comments
 (0)