-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathhub.py
187 lines (159 loc) · 6.92 KB
/
hub.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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
from flask import Flask, request, render_template, jsonify, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
import json
import datetime
import requests
# for local development - delete later
# from flask import Flask
from flask_cors import CORS
db = SQLAlchemy()
# Define the User data-model.
# NB: Make sure to add flask_user UserMixin as this adds additional fields and properties required by Flask-User
class Channel(db.Model):
__tablename__ = 'channels'
id = db.Column(db.Integer, primary_key=True)
active = db.Column('is_active', db.Boolean(), nullable=False, server_default='1')
name = db.Column(db.String(100, collation='NOCASE'), nullable=False)
endpoint = db.Column(db.String(100, collation='NOCASE'), nullable=False, unique=True)
authkey = db.Column(db.String(100, collation='NOCASE'), nullable=False)
type_of_service = db.Column(db.String(100, collation='NOCASE'), nullable=False)
last_heartbeat = db.Column(db.DateTime(), nullable=True, server_default=None)
# Class-based application configuration
class ConfigClass(object):
""" Flask application config """
# Flask settings
SECRET_KEY = 'This is an INSECURE secret!! DO NOT use this in production!!'
# Flask-SQLAlchemy settings
SQLALCHEMY_DATABASE_URI = 'sqlite:///chat_server.sqlite' # File-based SQL database
SQLALCHEMY_TRACK_MODIFICATIONS = False # Avoids SQLAlchemy warning
# Create Flask app
app = Flask(__name__)
# DELETE BEFORE DEPLOYMENT!!
CORS(app) # this will allow CORS for all routes by default
app.config.from_object(__name__ + '.ConfigClass') # configuration
app.app_context().push() # create an app context before initializing db
db.init_app(app) # initialize database
db.create_all() # create database if necessary
SERVER_AUTHKEY = '1234567890'
STANDARD_CLIENT_URL = 'http://localhost:5005'
# The Home page is accessible to anyone
# @app.route('/')
# def home_page():
# # render home.html template
# channels = Channel.query.all()
# return render_template("home.html")
@app.route('/')
def home_page():
# find all active channels
channels = Channel.query.filter_by(active=True).all()
# render hub_home.html template
return render_template("hub_home.html", channels=channels, STANDARD_CLIENT_URL=STANDARD_CLIENT_URL)
def health_check(endpoint, authkey):
# make GET request to URL
# add authkey to request header
# response = requests.get(endpoint+'/health',
# headers={'Authorization': 'authkey '+authkey})
try:
response = requests.get(endpoint + '/health',
headers={'Authorization': 'authkey ' + authkey})
except requests.exceptions.RequestException as e:
print(f"Error: {e}")
return False
if response.status_code != 200:
return False
# check if response is JSON with {"name": <channel_name>}
if 'name' not in response.json():
return False
# check if channel name is as expected
# (channels can't change their name, must be re-registered)
channel = Channel.query.filter_by(endpoint=endpoint).first()
channel.active = False
db.session.commit()
if not channel:
print(f"Channel {endpoint} not found in database")
return False
expected_name = channel.name
if response.json()['name'] != expected_name:
return False
# everything is OK, set last_heartbeat to now
channel.active = True
channel.last_heartbeat = datetime.datetime.now()
db.session.commit() # save to database
return True
# cli command to check health of all channels
@app.cli.command('check_channels')
def check_channels():
channels = Channel.query.all()
for channel in channels:
if not health_check(channel.endpoint, channel.authkey):
print(f"Channel {channel.endpoint} is not healthy")
else:
print(f"Channel {channel.endpoint} is healthy")
# Flask REST route for POST to /channels
@app.route('/channels', methods=['POST'])
def create_channel():
global SERVER_AUTHKEY
record = json.loads(request.data)
# check if authorization header is present
if 'Authorization' not in request.headers:
return "No authorization header", 400
# check if authorization header is valid
if request.headers['Authorization'] != 'authkey ' + SERVER_AUTHKEY:
return "Invalid authorization header ({})".format(request.headers['Authorization']), 400
if 'name' not in record:
return "Record has no name", 400
if 'endpoint' not in record:
return "Record has no endpoint", 400
if 'authkey' not in record:
return "Record has no authkey", 400
if 'type_of_service' not in record:
return "Record has no type of service representation", 400
update_channel = Channel.query.filter_by(endpoint=record['endpoint']).first()
print("update_channel: ", update_channel)
if update_channel: # Channel already exists, update it
update_channel.name = record['name']
update_channel.authkey = record['authkey']
update_channel.type_of_service = record['type_of_service']
update_channel.active = False
db.session.commit()
if not health_check(record['endpoint'], record['authkey']):
return "Channel is not healthy", 400
return jsonify(created=False,
id=update_channel.id), 200
else: # new channel, create it
channel = Channel(name=record['name'],
endpoint=record['endpoint'],
authkey=record['authkey'],
type_of_service=record['type_of_service'],
last_heartbeat=datetime.datetime.now(),
active=True)
db.session.add(channel)
db.session.commit()
if not health_check(record['endpoint'], record['authkey']):
# delete channel from database
db.session.delete(channel)
db.session.commit()
return "Channel is not healthy", 400
return jsonify(created=True, id=channel.id), 200
@app.route('/channels', methods=['GET'])
def get_channels():
channels = Channel.query.all()
return jsonify(channels=[{'name': c.name,
'endpoint': c.endpoint,
'authkey': c.authkey,
'type_of_service': c.type_of_service} for c in channels]), 200
@app.route('/health', methods=['GET'])
def health():
# check either all channels or a specific channel (if id is provided)
if 'id' in request.args:
channel = Channel.query.filter_by(id=request.args['id']).first()
health_check(channel.endpoint, channel.authkey)
else:
channels = Channel.query.all()
for channel in channels:
health_check(channel.endpoint, channel.authkey)
# flask redirect to home page
return redirect(url_for('home_page'))
# Start development web server
if __name__ == '__main__':
app.run(port=5555, debug=True)