add hub-and-spoke diagrams to readme
This commit is contained in:
parent
b991ec89d2
commit
0a379b74ad
29
README.md
29
README.md
@ -121,6 +121,35 @@ In general, for safety, schema version upgrades should not delete data. Schema v
|
|||||||
|
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
## hub-and-spoke architecture
|
||||||
|
|
||||||
|
![](readme/hub-and-spoke1.png)
|
||||||
|
|
||||||
|
This diagram was created with https://app.diagrams.net/.
|
||||||
|
To edit it, download the <a download href="readme/hub-and-spoke.xml">diagram file</a> and edit it with the https://app.diagrams.net/ web application, or you may run the application from [source](https://github.com/jgraph/drawio) if you wish.
|
||||||
|
|
||||||
|
right now I have 2 types of operations, immediate mode and async.
|
||||||
|
|
||||||
|
both types of operations do assignment synchronously. so if the system cant assign the operation to one or more hosts (spokes),
|
||||||
|
or whatever the operation requires, then it will fail.
|
||||||
|
|
||||||
|
some operations tolerate partial failures, like, capacity_avaliable will succeed if at least one spoke succeeds
|
||||||
|
for immediate mode, assignment and completion of the operation (like `list`, `capacity_avaliable`, `destroy`) are the same thing
|
||||||
|
|
||||||
|
for async ones, they can be assigned without knowing whether or not they succeeded (`create`)
|
||||||
|
|
||||||
|
![](readme/hub-and-spoke2.png)
|
||||||
|
|
||||||
|
This diagram was created with https://app.diagrams.net/.
|
||||||
|
To edit it, download the <a download href="readme/hub-and-spoke.xml">diagram file</a> and edit it with the https://app.diagrams.net/ web application, or you may run the application from [source](https://github.com/jgraph/drawio) if you wish.
|
||||||
|
|
||||||
|
if you issue a create, and it technically could go to any number of hosts, but only one host responds, it will succeed
|
||||||
|
but if you issue a create and somehow 2 hosts both think they own that task, it will fail and throw a big error. cuz it expects exactly 1 to own the create task
|
||||||
|
|
||||||
|
currently its not set up to do any polling. its not really like a queue at all. It's all immediate for the most part
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
## how to setup btcpay server
|
## how to setup btcpay server
|
||||||
|
|
||||||
Generate a private key and the accompanying bitpay SIN for the btcpay API client.
|
Generate a private key and the accompanying bitpay SIN for the btcpay API client.
|
||||||
|
@ -6,7 +6,7 @@ from werkzeug.exceptions import abort
|
|||||||
|
|
||||||
from capsulflask.db import get_model, my_exec_info_message
|
from capsulflask.db import get_model, my_exec_info_message
|
||||||
|
|
||||||
bp = Blueprint("hosts", __name__, url_prefix="/hosts")
|
bp = Blueprint("hub", __name__, url_prefix="/hub")
|
||||||
|
|
||||||
def authorized_for_host(id):
|
def authorized_for_host(id):
|
||||||
auth_header_value = request.headers.get('Authorization').replace("Bearer ", "")
|
auth_header_value = request.headers.get('Authorization').replace("Bearer ", "")
|
||||||
@ -17,6 +17,6 @@ def heartbeat(id):
|
|||||||
if authorized_for_host(id):
|
if authorized_for_host(id):
|
||||||
get_model().host_heartbeat(id)
|
get_model().host_heartbeat(id)
|
||||||
else:
|
else:
|
||||||
current_app.logger.info(f"/hosts/heartbeat/{id} returned 401: invalid token")
|
current_app.logger.info(f"/hub/heartbeat/{id} returned 401: invalid token")
|
||||||
return abort(401, "invalid host id or token")
|
return abort(401, "invalid host id or token")
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
|
|
||||||
|
import aiohttp
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask import request
|
from flask import request
|
||||||
@ -6,17 +7,19 @@ from werkzeug.exceptions import abort
|
|||||||
|
|
||||||
from capsulflask.db import get_model, my_exec_info_message
|
from capsulflask.db import get_model, my_exec_info_message
|
||||||
|
|
||||||
bp = Blueprint("hosts", __name__, url_prefix="/hosts")
|
bp = Blueprint("spoke", __name__, url_prefix="/spoke")
|
||||||
|
|
||||||
def authorized_for_host(id):
|
def authorized_as_hub(id):
|
||||||
auth_header_value = request.headers.get('Authorization').replace("Bearer ", "")
|
auth_header_value = request.headers.get('Authorization').replace("Bearer ", "")
|
||||||
return get_model().authorized_for_host(id, auth_header_value)
|
return auth_header_value == current_app.config["HUB_TOKEN"]
|
||||||
|
|
||||||
@bp.route("/heartbeat/<string:id>", methods=("POST"))
|
@bp.route("/heartbeat", methods=("POST"))
|
||||||
def heartbeat(id):
|
def heartbeat():
|
||||||
if authorized_for_host(id):
|
if authorized_as_hub(id):
|
||||||
get_model().host_heartbeat(id)
|
# make request to hub-domain.com/hub/heartbeat/{current_app.config["SPOKE_HOST_ID"]}
|
||||||
|
# succeed or fail based on whether the request succeeds or fails.
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
current_app.logger.info(f"/hosts/heartbeat/{id} returned 401: invalid token")
|
current_app.logger.info(f"/hosts/heartbeat returned 401: invalid token")
|
||||||
return abort(401, "invalid host id or token")
|
return abort(401, "invalid hub token")
|
||||||
|
|
||||||
|
@ -54,7 +54,6 @@ class MockSpoke(SpokeInterface):
|
|||||||
def destroy(self, email: str, id: str):
|
def destroy(self, email: str, id: str):
|
||||||
current_app.logger.info(f"mock destroy: {id} for {email}")
|
current_app.logger.info(f"mock destroy: {id} for {email}")
|
||||||
|
|
||||||
|
|
||||||
class ShellScriptSpoke(SpokeInterface):
|
class ShellScriptSpoke(SpokeInterface):
|
||||||
|
|
||||||
def validate_completed_process(self, completedProcess, email=None):
|
def validate_completed_process(self, completedProcess, email=None):
|
||||||
@ -157,17 +156,12 @@ class ShellScriptSpoke(SpokeInterface):
|
|||||||
{completedProcess.stderr}
|
{completedProcess.stderr}
|
||||||
""")
|
""")
|
||||||
|
|
||||||
def destroy(self, email: str, id: str):
|
def destroy(self, email: str, id: str) -> str:
|
||||||
validate_capsul_id(id)
|
validate_capsul_id(id)
|
||||||
completedProcess = run([join(current_app.root_path, 'shell_scripts/destroy.sh'), id], capture_output=True)
|
completedProcess = run([join(current_app.root_path, 'shell_scripts/destroy.sh'), id], capture_output=True)
|
||||||
self.validate_completed_process(completedProcess, email)
|
self.validate_completed_process(completedProcess, email)
|
||||||
lines = completedProcess.stdout.splitlines()
|
lines = completedProcess.stdout.splitlines()
|
||||||
status = lines[len(lines)-1].decode("utf-8")
|
status = lines[len(lines)-1].decode("utf-8")
|
||||||
|
return status
|
||||||
|
|
||||||
|
|
||||||
if not status == "success":
|
|
||||||
raise ValueError(f"""failed to destroy vm "{id}" for {email}:
|
|
||||||
stdout:
|
|
||||||
{completedProcess.stdout}
|
|
||||||
stderr:
|
|
||||||
{completedProcess.stderr}
|
|
||||||
""")
|
|
||||||
|
1
readme/hub-and-spoke.xml
Normal file
1
readme/hub-and-spoke.xml
Normal file
@ -0,0 +1 @@
|
|||||||
|
<mxfile host="app.diagrams.net" modified="2021-01-03T00:49:24.202Z" agent="5.0 (X11)" etag="bNLCzv8xp0QHJ2iOv8s8" version="14.1.8" type="device"><diagram id="2J8LUWl6CGm-7iVjfT5N" name="Page-1">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</diagram></mxfile>
|
BIN
readme/hub-and-spoke1.png
Normal file
BIN
readme/hub-and-spoke1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
BIN
readme/hub-and-spoke2.png
Normal file
BIN
readme/hub-and-spoke2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
Loading…
Reference in New Issue
Block a user