AETHER-1717 Validate JSON in request

Change-Id: Ia3891f6c1ff85a05f98031185ebd02576b968328
diff --git a/edge-monitoring/VERSION b/edge-monitoring/VERSION
index a918a2a..ee6cdce 100644
--- a/edge-monitoring/VERSION
+++ b/edge-monitoring/VERSION
@@ -1 +1 @@
-0.6.0
+0.6.1
diff --git a/edge-monitoring/edge_monitoring_server.py b/edge-monitoring/edge_monitoring_server.py
index f8f8c91..aa4d7e5 100755
--- a/edge-monitoring/edge_monitoring_server.py
+++ b/edge-monitoring/edge_monitoring_server.py
@@ -12,6 +12,7 @@
 from icalevents.icalevents import events
 from flask import Flask, jsonify, abort, request, Response
 import prometheus_client as prom
+import jsonschema
 
 # URL of maintenance calendar
 SECRET_ICAL_URL = os.environ.get("SECRET_ICAL_URL")
@@ -24,6 +25,64 @@
 NO_RESULT_THRESHOLD = 720
 
 app = Flask(__name__)
+
+edgeSchema = {
+    "type": "object",
+    "properties": {
+        "name": {"type": "string"},
+        "status": {
+            "type": "object",
+            "properties": {
+                "control_plane": {"type": "string"},
+                "user_plane": {"type": "string"}
+            },
+            "required": ["control_plane", "user_plane"]
+        },
+        "speedtest": {
+            "type": "object",
+            "properties": {
+                "ping": {
+                    "type": "object",
+                    "properties": {
+                        "dns": {
+                            "type": "object",
+                            "properties": {
+                                "min": {"type": "number"},
+                                "avg": {"type": "number"},
+                                "max": {"type": "number"},
+                                "stddev": {"type": "number"}
+                            },
+                            "required": ["min", "avg", "max", "stddev"]
+                        }
+                    }
+                },
+                "iperf": {
+                    "type": "object",
+                    "properties": {
+                        "cluster": {
+                            "type": "object",
+                            "properties": {
+                                "downlink": {"type": "number"},
+                                "uplink": {"type": "number"}
+                            },
+                            "required": ["downlink", "uplink"]
+                        }
+                    }
+                }
+            }
+        },
+        "signal_quality": {
+            "type": "object",
+            "properties": {
+                "rsrq": {"type": "number"},
+                "rsrp": {"type": "number"}
+            },
+            "required": ["rsrq", "rsrp"]
+        }
+    },
+    "required": ["name", "status"]
+}
+
 edges = [
     {
         'name': 'ace-example',
@@ -305,11 +364,10 @@
 @app.route('/edges', methods=['POST'])
 @app.route('/testresults', methods=['POST'])
 def create_or_update_edge():
-    if not request.json:
-        abort(400)
-    if 'name' not in request.json:
-        abort(400)
-    if 'status' not in request.json:
+    try:
+        jsonschema.validate(instance=request.json, schema=edgeSchema)
+    except jsonschema.exceptions.ValidationError as err:
+        print(err)
         abort(400)
 
     req_edge = {
diff --git a/edge-monitoring/requirements.txt b/edge-monitoring/requirements.txt
index 7b0752e..bc3f77b 100644
--- a/edge-monitoring/requirements.txt
+++ b/edge-monitoring/requirements.txt
@@ -2,3 +2,4 @@
 prometheus-client
 pytz
 icalevents
+jsonschema
diff --git a/edge-monitoring/test_edge_monitoring_server.py b/edge-monitoring/test_edge_monitoring_server.py
index 33e26f7..e326a06 100755
--- a/edge-monitoring/test_edge_monitoring_server.py
+++ b/edge-monitoring/test_edge_monitoring_server.py
@@ -10,6 +10,7 @@
 import pytz
 import json
 import time
+from copy import deepcopy
 
 
 test_edge = {
@@ -243,7 +244,7 @@
 
         response = self.app.get('/edges/metrics')
         data = response.get_data(as_text=True)
-        print(data)
+        # print(data)
 
         self._assert_status_metrics_exist(data)
         self._assert_speedtest_metrics_exist(data)
@@ -255,7 +256,7 @@
 
         response = self.app.get('/edges/metrics')
         data = response.get_data(as_text=True)
-        print(data)
+        # print(data)
         self.assertFalse('ace-menlo-pixel' in data)
 
         response = self.app.get('/edges')
@@ -273,7 +274,7 @@
 
         response = self.app.get('/edges/metrics')
         data = response.get_data(as_text=True)
-        print(data)
+        # print(data)
 
         self._assert_status_metrics_exist(data)
         self._assert_speedtest_metrics_exist(data)
@@ -285,7 +286,7 @@
 
         response = self.app.get('/edges/metrics')
         data = response.get_data(as_text=True)
-        print(data)
+        # print(data)
         self.assertFalse('ace-menlo-pixel' in data)
 
         response = self.app.get('/edges')
@@ -299,7 +300,7 @@
 
         response = self.app.get('/edges/metrics')
         data = response.get_data(as_text=True)
-        print(data)
+        # print(data)
 
         self._assert_speedtest_metrics_exist(data)
 
@@ -314,7 +315,7 @@
 
         response = self.app.get('/edges/metrics')
         data = response.get_data(as_text=True)
-        print(data)
+        # print(data)
 
         self.assertFalse('aetheredge_signal_quality_rsrq{name="ace-menlo-pixel"}' in data)
         self.assertFalse('aetheredge_signal_quality_rsrp{name="ace-menlo-pixel"}' in data)
@@ -332,7 +333,7 @@
 
         response = self.app.get('/edges/metrics')
         data = response.get_data(as_text=True)
-        print(data)
+        # print(data)
 
         self.assertFalse('aetheredge_ping_dns_test_min{name="ace-menlo-pixel"}' in data)
         self.assertFalse('aetheredge_ping_dns_test_avg{name="ace-menlo-pixel"}' in data)
@@ -355,7 +356,7 @@
 
         response = self.app.get('/edges/metrics')
         data = response.get_data(as_text=True)
-        print(data)
+        # print(data)
 
         self.assertFalse('aetheredge_signal_quality_rsrq{name="ace-menlo-pixel"}' in data)
         self.assertFalse('aetheredge_signal_quality_rsrp{name="ace-menlo-pixel"}' in data)
@@ -370,7 +371,7 @@
     def test_timeout_stale_result(self):
         response = self.app.post('/testresults', json=test_edge)
         data = json.loads(response.get_data(as_text=True))
-        print(json.dumps(data, indent=2))
+        # print(json.dumps(data, indent=2))
 
         self.assertEqual(data['edge']['status']['control_plane'], 'connected')
         self.assertEqual(data['edge']['status']['user_plane'], 'connected')
@@ -381,7 +382,7 @@
 
         response = self.app.get('/edges/ace-menlo-pixel')
         data = json.loads(response.get_data(as_text=True))
-        print(json.dumps(data, indent=2))
+        # print(json.dumps(data, indent=2))
 
         self.assertEqual(data['edge']['status']['control_plane'], 'no result')
         self.assertEqual(data['edge']['status']['user_plane'], 'no result')
@@ -395,7 +396,7 @@
 
         response = self.app.get('/edges/metrics')
         data = response.get_data(as_text=True)
-        print(data)
+        # print(data)
 
         self.assertFalse('iperf_cluster_downlink{name="ace-menlo-pixel"}' in data)
         self.assertFalse('iperf_cluster_uplink{name="ace-menlo-pixel"}' in data)
@@ -404,6 +405,57 @@
         data = json.loads(response.get_data(as_text=True))
         self.assertEqual(data['result'], True)
 
+    def test_handle_invalid_schema(self):
+        response = self.app.post('/testresults', json="")
+        self.assertEqual(response.status_code, 400)
+
+        no_name = deepcopy(test_edge)
+        del no_name['name']
+        response = self.app.post('/testresults', json=no_name)
+        self.assertEqual(response.status_code, 400)
+        response = self.app.get('/edges/metrics')
+        data = response.get_data(as_text=True)
+        self.assertFalse('ace-menlo-pixel' in data)
+
+        no_status = deepcopy(test_edge)
+        del no_status['status']
+        response = self.app.post('/testresults', json=no_status)
+        self.assertEqual(response.status_code, 400)
+        response = self.app.get('/edges/metrics')
+        data = response.get_data(as_text=True)
+        self.assertFalse('ace-menlo-pixel' in data)
+
+        bad_status = deepcopy(test_edge)
+        bad_status['status']['control_plane'] = 1
+        response = self.app.post('/testresults', json=bad_status)
+        self.assertEqual(response.status_code, 400)
+        response = self.app.get('/edges/metrics')
+        data = response.get_data(as_text=True)
+        self.assertFalse('ace-menlo-pixel' in data)
+
+        bad_ping_result = deepcopy(test_edge)
+        bad_ping_result['speedtest']['ping']['dns']['min'] = "foo"
+        response = self.app.post('/testresults', json=bad_ping_result)
+        self.assertEqual(response.status_code, 400)
+        response = self.app.get('/edges/metrics')
+        data = response.get_data(as_text=True)
+        self.assertFalse('ace-menlo-pixel' in data)
+
+        bad_iperf_result = deepcopy(test_edge)
+        bad_iperf_result['speedtest']['iperf']['cluster']['uplink'] = "foo"
+        response = self.app.post('/testresults', json=bad_iperf_result)
+        self.assertEqual(response.status_code, 400)
+        response = self.app.get('/edges/metrics')
+        data = response.get_data(as_text=True)
+        self.assertFalse('ace-menlo-pixel' in data)
+
+        bad_signal_quality = deepcopy(test_edge)
+        del bad_signal_quality['signal_quality']['rsrq']
+        response = self.app.post('/testresults', json=bad_signal_quality)
+        self.assertEqual(response.status_code, 400)
+        response = self.app.get('/edges/metrics')
+        data = response.get_data(as_text=True)
+        self.assertFalse('ace-menlo-pixel' in data)
 
 if __name__ == '__main__':
     suite = unittest.TestLoader().loadTestsFromTestCase(TestEdgeMonitoringServer)