-
Notifications
You must be signed in to change notification settings - Fork 0
/
zookeeperd.py
executable file
·134 lines (109 loc) · 3.88 KB
/
zookeeperd.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#!/usr/bin/env python3
import configparser
import json
import random
import threading
import time
import uuid
import flask
from werkzeug.exceptions import HTTPException
app = flask.Flask(__name__)
# Where we keep our threads. A real world implementation would probably use a
# more sophisticated approach
threads = {}
# The "language" by which we compute the status
language = {
'animals': [],
'verbs': [],
'adjectives': [],
'adverbs': [],
}
class ZookeeperException(HTTPException):
""" Generic base exception class for zoo-related problems. It inherits
from HTTPException to allow for simplified flask "over-HTTP" handling
"""
code = 500
def read_config():
""" Simplistic config file input without content verification. The filename
is hardcoded "zookeeper.conf". It has to have sections for every
element of the language. Each section must contain key-only lines.
"""
config = configparser.ConfigParser(allow_no_value=True)
config.read('zookeeper.conf')
# Optimistically assumes that the file sections match the language
for block in language.keys():
for (key, val) in config.items(block):
language[block].append(key)
app.logger.info(f'language: {language}')
def build_status(animal) -> str:
""" Create the "status" of the animals by randomly building a sentence with
fixed assembly logic "adjective animal verb adverb"
"""
adjective = random.choice(language['adjectives']).capitalize()
verb = random.choice(language['verbs'])
adverb = random.choice(language['adverbs'])
return f'{adjective} {animal} {verb} {adverb}'
def worker(id: uuid.UUID, animal: str):
""" The thread emulating the workload. It randomly waits for 10-20 seconds
and then assigns the animals status as the threads value. This
also eliminates the reference to the thread
"""
app.logger.debug('thread start')
time.sleep(random.randrange(10, 20))
threads[id] = build_status(animal)
app.logger.debug('thread end')
return
def add_thread(animal) -> uuid.UUID:
""" Create a new worker thread and add it to the global threads
dictionary
"""
# see https://docs.python.org/3/library/uuid.html for variations
new_uuid = uuid.uuid4()
thread = threading.Thread(target=worker, args=(new_uuid, animal))
threads[new_uuid] = thread
thread.start()
return new_uuid
@app.errorhandler(HTTPException)
def handle_exception(e):
""" Allows the use of regular exceptions in the code by writing it to this
process' log as well as returning it as a HTTP response
"""
app.logger.exception(e)
"""Return JSON instead of HTML for HTTP errors."""
response = e.get_response()
response.data = json.dumps({
"code": e.code,
"name": e.name,
"description": e.description,
})
response.content_type = "application/json"
return response
@app.route('/animals')
def get_animals():
""" Get a list of animals in the zoo. Any animal can be queried
"""
return json.dumps(language['animals'])
@app.route('/animals/<string:animal>')
def start_query(animal):
""" Start a status query for an animal
"""
if animal in language['animals']:
id = add_thread(animal)
return json.dumps(str(id))
else:
raise ZookeeperException(f'No such animal: {animal}')
@app.route('/query/<uuid:id>')
def query_result(id):
""" Show the current result of the query. An ongoing query is reflected
by "...". An ongoing query is also sent with a HTTP 202 code to signal
incompleteness to the caller
"""
if id not in threads.keys():
raise ZookeeperException(f'No query with id {id}')
if isinstance(threads[id], threading.Thread):
return json.dumps('...'), 202
else:
return json.dumps(threads[id])
if __name__ == "__main__":
read_config()
app.run()