blob: 1bea10c84ac1cae7d83810b67fca7700611aff3c [file] [log] [blame]
Hyunsun Moonf32ae9a2020-05-28 13:17:45 -07001#!/usr/bin/env python
2
3# Copyright 2020-present Open Networking Foundation
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
Andy Bavier614af142020-08-07 14:49:56 -070017import os
Hyunsun Moonf32ae9a2020-05-28 13:17:45 -070018import time
Andy Bavier614af142020-08-07 14:49:56 -070019import datetime
20import pytz
21import threading
22from icalevents.icalevents import events
Andy Bavier4021a2f2020-07-29 12:39:47 -070023from flask import Flask, jsonify, abort, request, Response
24import prometheus_client as prom
25
Andy Bavier614af142020-08-07 14:49:56 -070026SECRET_ICAL_URL = os.environ.get("SECRET_ICAL_URL")
Andy Bavier4021a2f2020-07-29 12:39:47 -070027NO_RESULT_THRESHOLD = 720
Hyunsun Moonf32ae9a2020-05-28 13:17:45 -070028
29app = Flask(__name__)
30edges = [
31 {
32 'name': 'production-edge-example',
33 'status': {
34 'control_plane': 'connected',
35 'user_plane': 'connected'
36 },
Andy Bavier614af142020-08-07 14:49:56 -070037 'last_update': time.time(),
Hyunsun Moonf32ae9a2020-05-28 13:17:45 -070038 }
39]
40
Andy Bavier4021a2f2020-07-29 12:39:47 -070041status_codes = {
42 "no result": -2,
43 "error": -1,
44 "disconnected": 0,
45 "connecting": 1,
46 "connected": 2
47}
48
Andy Bavier614af142020-08-07 14:49:56 -070049room_mapping = {
50 "production-edge-onf-menlo": "(Compute)-MP-1-Aether Production",
51 "production-edge-example": "(Compute)-MP-1-Aether Production" # for testing
52}
53
Andy Bavier4021a2f2020-07-29 12:39:47 -070054cp_status = prom.Gauge("aetheredge_status_control_plane", "Control plane status code", ["name"])
55up_status = prom.Gauge("aetheredge_status_user_plane", "User plane status code", ["name"])
56last_update = prom.Gauge("aetheredge_last_update", "Last reported test result", ["name"])
Andy Bavier614af142020-08-07 14:49:56 -070057maint_window = prom.Gauge("aetheredge_in_maintenance_window", "Currently in a maintenance window", ["name"])
58
59def is_my_event(event, name):
60 for field in ["summary", "location", "description"]:
61 if name in getattr(event, field, ""):
62 return True
63 return False
64
Andy Bavierc41cf0c2020-09-02 14:49:21 -070065def is_naive_datetime(d):
66 return d.tzinfo is None or d.tzinfo.utcoffset(d) is None
67
68def process_all_day_events(es):
69 for event in es:
70 if event.all_day:
71 # All day events have naive datetimes, which breaks comparisons
72 pacific = pytz.timezone('US/Pacific')
73 if is_naive_datetime(event.start):
74 event.start = pacific.localize(event.start)
75 if is_naive_datetime(event.end):
76 event.end = pacific.localize(event.end)
77
Andy Bavier614af142020-08-07 14:49:56 -070078def in_maintenance_window(events, name, now):
79 for event in events:
80 if event.start < now and event.end > now:
81 if is_my_event(event, name):
82 return True
83 if name in room_mapping and is_my_event(event, room_mapping[name]):
84 return True
85 return False
86
87def pull_maintenance_events():
88 while(True):
89 now = datetime.datetime.now(pytz.utc)
90 try:
91 es = events(SECRET_ICAL_URL, start = now)
Andy Bavierc41cf0c2020-09-02 14:49:21 -070092 process_all_day_events(es)
Andy Bavier614af142020-08-07 14:49:56 -070093 except Exception as e:
94 print(e)
95 else:
96 for edge in edges:
97 if 'maintenance' not in edge:
98 edge['maintenance'] = {}
99 edge['maintenance']['in_window'] = in_maintenance_window(es, edge['name'], now)
100 edge['maintenance']['last_update'] = time.time()
101 time.sleep(60)
Andy Bavier4021a2f2020-07-29 12:39:47 -0700102
103def time_out_stale_results():
104 for edge in edges:
105 time_elapsed = time.time() - edge["last_update"]
106 if time_elapsed > NO_RESULT_THRESHOLD:
107 edge['status']['control_plane'] = "no result"
108 edge['status']['user_plane'] = "no result"
109
110
111@app.route('/edges/metrics', methods=['GET'])
112def get_prometheus_metrics():
113 res = []
114 time_out_stale_results()
115 for edge in edges:
116 if edge['name'] == "production-edge-example":
117 continue
118
119 cp_status.labels(edge['name']).set(status_codes[edge['status']['control_plane']])
120 up_status.labels(edge['name']).set(status_codes[edge['status']['user_plane']])
121 last_update.labels(edge['name']).set(edge['last_update'])
Andy Bavier614af142020-08-07 14:49:56 -0700122 if 'maintenance' in edge:
123 maint_window.labels(edge['name']).set(int(edge['maintenance']['in_window']))
Andy Bavier4021a2f2020-07-29 12:39:47 -0700124
125 res.append(prom.generate_latest(cp_status))
126 res.append(prom.generate_latest(up_status))
127 res.append(prom.generate_latest(last_update))
Andy Bavier614af142020-08-07 14:49:56 -0700128 res.append(prom.generate_latest(maint_window))
129
Andy Bavier4021a2f2020-07-29 12:39:47 -0700130 return Response(res, mimetype="text/plain")
131
Hyunsun Moonf32ae9a2020-05-28 13:17:45 -0700132
133@app.route('/edges/healthz', methods=['GET'])
134def get_health():
135 return {'message': 'healthy'}
136
137
138@app.route('/edges', methods=['GET'])
139def get_edges():
Andy Bavier4021a2f2020-07-29 12:39:47 -0700140 time_out_stale_results()
Hyunsun Moonf32ae9a2020-05-28 13:17:45 -0700141 return jsonify({'edges': edges})
142
143
144@app.route('/edges/<string:name>', methods=['GET'])
145def get_edge(name):
Andy Bavier4021a2f2020-07-29 12:39:47 -0700146 time_out_stale_results()
Hyunsun Moonf32ae9a2020-05-28 13:17:45 -0700147 edge = [edge for edge in edges if edge['name'] == name]
148 if len(edge) == 0:
149 abort(404)
150 return jsonify({'edge': edge[0]})
151
152
153@app.route('/edges', methods=['POST'])
154def create_or_update_edge():
155 if not request.json:
156 abort(400)
157 if 'name' not in request.json:
158 abort(400)
159 if 'status' not in request.json:
160 abort(400)
161
162 req_edge = {
163 'name': request.json['name'],
164 'status': {
165 'control_plane': request.json['status']['control_plane'],
166 'user_plane': request.json['status']['user_plane']
167 },
168 'last_update': time.time()
169 }
170
171 edge = [edge for edge in edges if edge['name'] == req_edge['name']]
172 if len(edge) == 0:
173 print("new edge request " + req_edge['name'])
174 edges.append(req_edge)
175 else:
176 edge[0]['status']['control_plane'] = req_edge['status']['control_plane']
177 edge[0]['status']['user_plane'] = req_edge['status']['user_plane']
178 edge[0]['last_update'] = req_edge['last_update']
179
180 return jsonify({'edge': req_edge}), 201
181
182
Hyunsun Moon5f237ec2020-09-29 14:45:52 -0700183@app.route('/edges/<string:name>', methods=['DELETE'])
184def delete_edge(name):
185 print("delete edge request " + name)
186 result = False
187 for i in range(len(edges)):
188 if edges[i]['name'] == name:
189 del edges[i]
190 result = True
191 break
192 if not result:
193 abort(404)
194 return jsonify({'result': True})
195
196
Hyunsun Moonf32ae9a2020-05-28 13:17:45 -0700197if __name__ == '__main__':
Andy Bavier614af142020-08-07 14:49:56 -0700198 if SECRET_ICAL_URL:
199 print(" * Starting maintenance calendar polling thread")
200 t = threading.Thread(target=pull_maintenance_events)
201 t.start()
Hyunsun Moonf32ae9a2020-05-28 13:17:45 -0700202 app.run(debug=True, host='0.0.0.0', port=80)