Skip to content

Commit 324e530

Browse files
authored
Merge pull request #35 from KoleBarnes/feature/fastapi
Add support for running as an API server.
2 parents 7b098af + 01acb3c commit 324e530

File tree

18 files changed

+548
-162
lines changed

18 files changed

+548
-162
lines changed

fetch-validator-status/DidKey.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55

66
class DidKey:
77
def __init__(self, seed):
8-
seed = seed_as_bytes(seed)
9-
self.sk = nacl.signing.SigningKey(seed)
8+
self.seed = seed
9+
self.seed = self.seed_as_bytes()
10+
self.sk = nacl.signing.SigningKey(self.seed)
1011
self.vk = bytes(self.sk.verify_key)
1112
self.did = base58.b58encode(self.vk[:16]).decode("ascii")
1213
self.verkey = base58.b58encode(self.vk).decode("ascii")
@@ -15,9 +16,9 @@ def sign_request(self, req: Request):
1516
signed = self.sk.sign(req.signature_input)
1617
req.set_signature(signed.signature)
1718

18-
def seed_as_bytes(seed):
19-
if not seed or isinstance(seed, bytes):
20-
return seed
21-
if len(seed) != 32:
22-
return base64.b64decode(seed)
23-
return seed.encode("ascii")
19+
def seed_as_bytes(self):
20+
if not self.seed or isinstance(self.seed, bytes):
21+
return self.seed
22+
if len(self.seed) != 32:
23+
return base64.b64decode(self.seed)
24+
return self.seed.encode("ascii")

fetch-validator-status/Dockerfile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ RUN apt-get update -y && \
1010
rm -rf /var/lib/apt/lists/*
1111
USER $user
1212

13-
RUN pip install pynacl gspread oauth2client
13+
ADD requirements.txt .
14+
RUN pip install --no-cache-dir -r requirements.txt
1415

1516
ADD networks.json .
1617
ADD *.py ./
1718

18-
ENTRYPOINT ["bash", "-c", "python fetch_status.py $@", "--"]
19+
ENTRYPOINT ["bash", "-c", "python main.py $@", "--"]

fetch-validator-status/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ Note that there are three different formats for the timestamps in the data struc
171171

172172
For info on plug-ins see the plug-ins [readme](plugins/README.md)
173173

174+
## Rest API
175+
176+
For info on Rest API see [REST API](REST_API.md)
177+
174178
### Running against other Indy Networks
175179

176180
To see the validator info against any other Indy network, you need a URL for the Genesis file for the network, and the seed for a suitably authorized DID. The pool Genesis file URLs are easy, since that is published data needed by agents connecting to Indy networks. Sovrin genesis URLs can be found [here](https://github.com/sovrin-foundation/sovrin/tree/master/sovrin). You need the URL for the raw version of the pool transaction files. For example, the URL you need for the Sovrin MainNet is:

fetch-validator-status/REST_API.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Rest API
2+
3+
To run [fetch validator](README.md) as a webAPI `cd fetch-validator-status` and `IM=1 ./run.sh --web -v` to start the server.
4+
To run in debug mode add `--debug`.
5+
6+
## How To Use
7+
8+
After running the command above. Go to http://localhost:8080/ in your browser. Then click on one of the colored drop downs and click the 'Try it out' button. Fill out any required fields then click 'execute'. This will give you a response with a, curl command, request url, and response body.
Lines changed: 47 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -1,139 +1,55 @@
1-
import argparse
2-
import asyncio
3-
# import base58
4-
# import base64
5-
import json
6-
import os
7-
import sys
8-
# import datetime
9-
import urllib.request
10-
# from typing import Tuple
11-
12-
# import nacl.signing
13-
14-
import indy_vdr
151
from indy_vdr.ledger import (
162
build_get_validator_info_request,
173
build_get_txn_request,
18-
# Request,
194
)
20-
from indy_vdr.pool import open_pool
5+
from util import log
216
from plugin_collection import PluginCollection
22-
# import time
237
from DidKey import DidKey
8+
from pool import PoolCollection
9+
from singleton import Singleton
10+
11+
class NodeNotFound(Exception):
12+
pass
13+
14+
class FetchStatus(object, metaclass=Singleton):
15+
def __init__(self, verbose, pool_collection: PoolCollection):
16+
self.verbose = verbose
17+
self.pool_collection = pool_collection
18+
19+
async def fetch(self, network_id: str, monitor_plugins: PluginCollection, nodes: str = None, ident: DidKey = None):
20+
result = []
21+
verifiers = {}
22+
23+
pool, network_name = await self.pool_collection.get_pool(network_id)
24+
if ident:
25+
log(f"Building request with did: {ident.did} ...")
26+
request = build_get_validator_info_request(ident.did)
27+
ident.sign_request(request)
28+
else:
29+
log("Building an anonymous request ...")
30+
request = build_get_txn_request(None, 1, 1)
31+
32+
from_nodes = []
33+
if nodes:
34+
from_nodes = nodes.split(",")
2435

25-
verbose = False
26-
27-
28-
def log(*args):
29-
if verbose:
30-
print(*args, "\n", file=sys.stderr)
31-
32-
33-
async def fetch_status(genesis_path: str, nodes: str = None, ident: DidKey = None, network_name: str = None):
34-
# Start Of Engine
35-
attempt = 3
36-
while attempt:
3736
try:
38-
pool = await open_pool(transactions_path=genesis_path)
39-
except:
40-
log("Pool Timed Out! Trying again...")
41-
if not attempt:
42-
print("Unable to get pool Response! 3 attempts where made. Exiting...")
43-
exit()
44-
attempt -= 1
45-
continue
46-
break
47-
48-
result = []
49-
verifiers = {}
50-
51-
if ident:
52-
request = build_get_validator_info_request(ident.did)
53-
ident.sign_request(request)
54-
else:
55-
request = build_get_txn_request(None, 1, 1)
56-
57-
from_nodes = []
58-
if nodes:
59-
from_nodes = nodes.split(",")
60-
response = await pool.submit_action(request, node_aliases = from_nodes)
61-
try:
62-
# Introduced in https://github.com/hyperledger/indy-vdr/commit/ce0e7c42491904e0d563f104eddc2386a52282f7
63-
verifiers = await pool.get_verifiers()
64-
except AttributeError:
65-
pass
66-
# End Of Engine
67-
68-
result = await monitor_plugins.apply_all_plugins_on_value(result, network_name, response, verifiers)
69-
print(json.dumps(result, indent=2))
70-
71-
def get_script_dir():
72-
return os.path.dirname(os.path.realpath(__file__))
73-
74-
75-
def download_genesis_file(url: str, target_local_path: str):
76-
log("Fetching genesis file ...")
77-
target_local_path = f"{get_script_dir()}/genesis.txn"
78-
urllib.request.urlretrieve(url, target_local_path)
79-
80-
def load_network_list():
81-
with open(f"{get_script_dir()}/networks.json") as json_file:
82-
networks = json.load(json_file)
83-
return networks
84-
85-
def list_networks():
86-
networks = load_network_list()
87-
return networks.keys()
88-
89-
if __name__ == "__main__":
90-
monitor_plugins = PluginCollection('plugins')
91-
92-
parser = argparse.ArgumentParser(description="Fetch the status of all the indy-nodes within a given pool.")
93-
parser.add_argument("--net", choices=list_networks(), help="Connect to a known network using an ID.")
94-
parser.add_argument("--list-nets", action="store_true", help="List known networks.")
95-
parser.add_argument("--genesis-url", default=os.environ.get('GENESIS_URL') , help="The url to the genesis file describing the ledger pool. Can be specified using the 'GENESIS_URL' environment variable.")
96-
parser.add_argument("--genesis-path", default=os.getenv("GENESIS_PATH") or f"{get_script_dir()}/genesis.txn" , help="The path to the genesis file describing the ledger pool. Can be specified using the 'GENESIS_PATH' environment variable.")
97-
parser.add_argument("-s", "--seed", default=os.environ.get('SEED') , help="The privileged DID seed to use for the ledger requests. Can be specified using the 'SEED' environment variable. If DID seed is not given the request will run anonymously.")
98-
parser.add_argument("--nodes", help="The comma delimited list of the nodes from which to collect the status. The default is all of the nodes in the pool.")
99-
parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose logging.")
100-
101-
monitor_plugins.get_parse_args(parser)
102-
args, unknown = parser.parse_known_args()
103-
104-
verbose = args.verbose
105-
106-
monitor_plugins.load_all_parse_args(args)
107-
108-
if args.list_nets:
109-
print(json.dumps(load_network_list(), indent=2))
110-
exit()
111-
112-
network_name = None
113-
if args.net:
114-
log("Loading known network list ...")
115-
networks = load_network_list()
116-
if args.net in networks:
117-
log("Connecting to '{0}' ...".format(networks[args.net]["name"]))
118-
args.genesis_url = networks[args.net]["genesisUrl"]
119-
network_name = networks[args.net]["name"]
120-
121-
if args.genesis_url:
122-
download_genesis_file(args.genesis_url, args.genesis_path)
123-
if not network_name:
124-
network_name = args.genesis_url
125-
if not os.path.exists(args.genesis_path):
126-
print("Set the GENESIS_URL or GENESIS_PATH environment variable or argument.\n", file=sys.stderr)
127-
parser.print_help()
128-
exit()
129-
130-
did_seed = None if not args.seed else args.seed
131-
132-
log("indy-vdr version:", indy_vdr.version())
133-
if did_seed:
134-
ident = DidKey(did_seed)
135-
log("DID:", ident.did, " Verkey:", ident.verkey)
136-
else:
137-
ident = None
138-
139-
asyncio.get_event_loop().run_until_complete(fetch_status(args.genesis_path, args.nodes, ident, network_name))
37+
# Introduced in https://github.com/hyperledger/indy-vdr/commit/ce0e7c42491904e0d563f104eddc2386a52282f7
38+
log("Getting list of verifiers ...")
39+
verifiers = await pool.get_verifiers()
40+
except AttributeError:
41+
log("Unable to get list of verifiers. Please make sure you have the latest version of indy-vdr.")
42+
pass
43+
44+
if verifiers and from_nodes:
45+
for node in from_nodes:
46+
if not node in verifiers:
47+
raise NodeNotFound(f'{node} is not a member of {network_name}.')
48+
49+
log("Submitting request ...")
50+
response = await pool.submit_action(request, node_aliases = from_nodes)
51+
52+
log("Passing results to plugins for processing ...")
53+
result = await monitor_plugins.apply_all_plugins_on_value(result, network_name, response, verifiers)
54+
log("Processing complete.")
55+
return result
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"""
2+
File created by tiangolo.
3+
https://github.com/tiangolo/uvicorn-gunicorn-docker/blob/master/docker-images/gunicorn_conf.py
4+
"""
5+
6+
import json
7+
import multiprocessing
8+
import os
9+
10+
workers_per_core_str = os.getenv("WORKERS_PER_CORE", "1")
11+
max_workers_str = os.getenv("MAX_WORKERS")
12+
use_max_workers = None
13+
if max_workers_str:
14+
use_max_workers = int(max_workers_str)
15+
web_concurrency_str = os.getenv("WEB_CONCURRENCY", None)
16+
17+
host = os.getenv("HOST", "0.0.0.0")
18+
port = os.getenv("PORT", "8080")
19+
bind_env = os.getenv("BIND", None)
20+
use_loglevel = os.getenv("LOG_LEVEL", "info")
21+
if bind_env:
22+
use_bind = bind_env
23+
else:
24+
use_bind = f"{host}:{port}"
25+
26+
cores = multiprocessing.cpu_count()
27+
workers_per_core = float(workers_per_core_str)
28+
default_web_concurrency = workers_per_core * cores
29+
if web_concurrency_str:
30+
web_concurrency = int(web_concurrency_str)
31+
assert web_concurrency > 0
32+
else:
33+
web_concurrency = max(int(default_web_concurrency), 2)
34+
if use_max_workers:
35+
web_concurrency = min(web_concurrency, use_max_workers)
36+
accesslog_var = os.getenv("ACCESS_LOG", "-")
37+
use_accesslog = accesslog_var or None
38+
errorlog_var = os.getenv("ERROR_LOG", "-")
39+
use_errorlog = errorlog_var or None
40+
graceful_timeout_str = os.getenv("GRACEFUL_TIMEOUT", "120")
41+
timeout_str = os.getenv("TIMEOUT", "120")
42+
keepalive_str = os.getenv("KEEP_ALIVE", "5")
43+
44+
# Gunicorn config variables
45+
loglevel = use_loglevel
46+
workers = web_concurrency
47+
bind = use_bind
48+
errorlog = use_errorlog
49+
worker_tmp_dir = "/dev/shm"
50+
accesslog = use_accesslog
51+
graceful_timeout = int(graceful_timeout_str)
52+
timeout = int(timeout_str)
53+
keepalive = int(keepalive_str)
54+
55+
56+
# For debugging and testing
57+
log_data = {
58+
"loglevel": loglevel,
59+
"workers": workers,
60+
"bind": bind,
61+
"graceful_timeout": graceful_timeout,
62+
"timeout": timeout,
63+
"keepalive": keepalive,
64+
"errorlog": errorlog,
65+
"accesslog": accesslog,
66+
# Additional, non-gunicorn variables
67+
"workers_per_core": workers_per_core,
68+
"use_max_workers": use_max_workers,
69+
"host": host,
70+
"port": port,
71+
}
72+
print('gunicorn config:')
73+
print(json.dumps(log_data, indent=2))

0 commit comments

Comments
 (0)