AETHER-8 Add a script for daily status report to Slack channel
Change-Id: Ib5e7102d968961e1ca071eaf8a006cc67a4d5c9b
diff --git a/edge-monitoring/Dockerfile.server b/edge-monitoring/Dockerfile.server
new file mode 100644
index 0000000..3648f2b
--- /dev/null
+++ b/edge-monitoring/Dockerfile.server
@@ -0,0 +1,22 @@
+# Copyright 2020-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License
+
+FROM python:3.7-slim
+
+WORKDIR /usr/src/app
+COPY requirements.txt ./
+RUN pip install --no-cache-dir -r requirements.txt
+COPY edge_monitoring_server.py ./
+
+CMD ["python", "edge_monitoring_server.py"]
diff --git a/edge-monitoring/config.json b/edge-monitoring/config.json
new file mode 100644
index 0000000..8f8a051
--- /dev/null
+++ b/edge-monitoring/config.json
@@ -0,0 +1,11 @@
+{
+ "edge_name": "production-edge-onf-menlo",
+ "adb": {
+ "path": "/usr/local/bin/adb",
+ "apn_mode_toggle_location": {
+ "x": "550",
+ "y": "700"
+ }
+ },
+ "report_url": "https://aether.onlab.us/edges"
+}
diff --git a/edge-monitoring/edge_monitoring_agent.py b/edge-monitoring/edge_monitoring_agent.py
new file mode 100644
index 0000000..bcfd5b5
--- /dev/null
+++ b/edge-monitoring/edge_monitoring_agent.py
@@ -0,0 +1,171 @@
+#!/usr/bin/env python
+
+# Copyright 2020-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License
+
+import os
+import time
+import requests
+import json
+import enum
+import daemon
+from collections import namedtuple
+from pyadb import ADB
+
+'''
+Check Aether network operational status and report it to
+central monitoring server
+
+1) check mobile connctivity after toggling the airplane mode
+2) check if ping to 8.8.8.8 works
+'''
+
+CONF = json.loads(
+ open(os.getenv('CONFIG_FILE', "./config.json")).read(),
+ object_hook=lambda d: namedtuple('X', d.keys())(*d.values())
+)
+
+ADB_GET_COMMANDS = {
+ "apn_mode": "settings get global airplane_mode_on",
+ "lte_state": "dumpsys telephony.registry | grep -m1 mDataConnectionState",
+ "ping_result": "ping -c 3 8.8.8.8&>/dev/null; echo $?"
+}
+ADB_APN_COMMANDS = {
+ "home": "input keyevent 3",
+ "setting": "am start -a android.settings.AIRPLANE_MODE_SETTINGS",
+ "toggle": "input tap " + \
+ CONF.adb.apn_mode_toggle_location.x + " " + \
+ CONF.adb.apn_mode_toggle_location.y
+}
+
+
+class State(enum.Enum):
+ error = "-1"
+ disconnected = "0"
+ connecting = "1"
+ connected = "2"
+
+ @classmethod
+ def has_value(cls, value):
+ return value in cls._value2member_map_
+
+
+edge_status = {
+ 'name': CONF.edge_name,
+ 'status': {
+ 'control_plane': None,
+ 'user_plane': 'connected'
+ }
+}
+
+
+def _run_adb_shell(adb, command):
+ result = adb.shell_command(command)
+ if adb.lastFailed():
+ err = "[ERROR]: " + command + " failed"
+ return False, err
+ time.sleep(2)
+ result = result[0] if result is not None else None
+ return True, result
+
+
+def get_control_plane_state():
+ '''
+ check aether control plane works by toggling airplane mode
+ '''
+ adb = ADB()
+ if adb.set_adb_path(CONF.adb.path) is False:
+ err = "[ERROR]: " + CONF.adb.path + " not found"
+ return State.error, err
+
+ # get the current airplane mode
+ success, result = _run_adb_shell(adb, ADB_GET_COMMANDS['apn_mode'])
+ if not success or result is None:
+ return State.error, result
+ apn_mode_on = True if result == "1" else False
+
+ # toggle the airplane mode
+ for command in ADB_APN_COMMANDS.values():
+ success, result = _run_adb_shell(adb, command)
+ if not success:
+ return State.error, result
+ if not apn_mode_on:
+ success, result = _run_adb_shell(adb, ADB_APN_COMMANDS['toggle'])
+ if not success:
+ return State.error, result
+
+ # additional wait for UE to fully attach
+ time.sleep(3)
+
+ # get connection state
+ state = State.connecting.value
+ while state == State.connecting.value:
+ success, result = _run_adb_shell(adb, ADB_GET_COMMANDS['lte_state'])
+ if not success or result is None:
+ return State.error, result
+ state = result.split("=")[1]
+
+ if not State.has_value(state):
+ return State.error, None
+ return State(state), None
+
+
+def get_user_plane_state():
+ '''
+ checks aether user plane connectivity with ping to 8.8.8.8
+ '''
+ adb = ADB()
+ if adb.set_adb_path(CONF.adb.path) is False:
+ err = "[ERROR]: " + CONF.adb.path + " not found"
+ return State.error, err
+
+ success, result = _run_adb_shell(adb, ADB_GET_COMMANDS['ping_result'])
+ if not success or result is None:
+ return State.error, result
+
+ state = State.connected if result == "0" else State.disconnected
+ return state, None
+
+
+def report_aether_network_state():
+ '''
+ report the aether network state to the monitoring server
+ '''
+ response = requests.post(CONF.report_url, json=edge_status)
+ return requests.codes.ok,
+ if response == requests.codes.ok:
+ print("[INFO]: reported the status")
+ else:
+ response.raise_for_status()
+
+
+def run():
+ while True:
+ cp_state, err = get_control_plane_state()
+ up_state, err = get_user_plane_state()
+
+ edge_status['status']['control_plane'] = cp_state.name
+ edge_status['status']['user_plane'] = up_state.name
+
+ report_aether_network_state()
+ time.sleep(600)
+
+
+def main():
+ with daemon.DaemonContext():
+ run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/edge-monitoring/edge_monitoring_server.py b/edge-monitoring/edge_monitoring_server.py
new file mode 100755
index 0000000..ed2088c
--- /dev/null
+++ b/edge-monitoring/edge_monitoring_server.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+
+# Copyright 2020-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import time
+from flask import Flask, jsonify, abort, request
+
+app = Flask(__name__)
+edges = [
+ {
+ 'name': 'production-edge-example',
+ 'status': {
+ 'control_plane': 'connected',
+ 'user_plane': 'connected'
+ },
+ 'last_update': time.time()
+ }
+]
+
+
+@app.route('/edges/healthz', methods=['GET'])
+def get_health():
+ return {'message': 'healthy'}
+
+
+@app.route('/edges', methods=['GET'])
+def get_edges():
+ return jsonify({'edges': edges})
+
+
+@app.route('/edges/<string:name>', methods=['GET'])
+def get_edge(name):
+ edge = [edge for edge in edges if edge['name'] == name]
+ if len(edge) == 0:
+ abort(404)
+ return jsonify({'edge': edge[0]})
+
+
+@app.route('/edges', 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:
+ abort(400)
+
+ req_edge = {
+ 'name': request.json['name'],
+ 'status': {
+ 'control_plane': request.json['status']['control_plane'],
+ 'user_plane': request.json['status']['user_plane']
+ },
+ 'last_update': time.time()
+ }
+
+ edge = [edge for edge in edges if edge['name'] == req_edge['name']]
+ if len(edge) == 0:
+ print("new edge request " + req_edge['name'])
+ edges.append(req_edge)
+ else:
+ edge[0]['status']['control_plane'] = req_edge['status']['control_plane']
+ edge[0]['status']['user_plane'] = req_edge['status']['user_plane']
+ edge[0]['last_update'] = req_edge['last_update']
+
+ return jsonify({'edge': req_edge}), 201
+
+
+if __name__ == '__main__':
+ app.run(debug=True, host='0.0.0.0', port=80)
diff --git a/edge-monitoring/edge_monitoring_server_k8s.yaml b/edge-monitoring/edge_monitoring_server_k8s.yaml
new file mode 100644
index 0000000..49a21e2
--- /dev/null
+++ b/edge-monitoring/edge_monitoring_server_k8s.yaml
@@ -0,0 +1,67 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: edge-monitoring
+spec:
+ finalizers:
+ - kubernetes
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: edge-monitoring-server
+ labels:
+ app: edge-monitoring-server
+ namespace: edge-monitoring
+spec:
+ selector:
+ matchLabels:
+ app: edge-monitoring-server
+ replicas: 1
+ strategy:
+ type: RollingUpdate
+ template:
+ metadata:
+ labels:
+ app: edge-monitoring-server
+ spec:
+ containers:
+ - name: server
+ image: docker.io/omecproject/edge-monitoring-server:0.1.0
+ imagePullPolicy: Always
+ command: ["python", "edge_monitoring_server.py"]
+ livenessProbe:
+ httpGet:
+ path: /edges/healthz
+ port: 80
+ initialDelaySeconds: 3
+ periodSeconds: 3
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: edge-monitoring-server
+ namespace: edge-monitoring
+spec:
+ selector:
+ app: edge-monitoring-server
+ ports:
+ - port: 80
+ targetPort: 80
+ protocol: TCP
+ name: server
+---
+apiVersion: extensions/v1beta1
+kind: Ingress
+metadata:
+ name: edge-monitoring-server
+ namespace: edge-monitoring
+spec:
+ rules:
+ - host: aether.onlab.us
+ http:
+ paths:
+ - backend:
+ serviceName: edge-monitoring-server
+ servicePort: 80
+ path: /edges
\ No newline at end of file
diff --git a/edge-monitoring/requirements.txt b/edge-monitoring/requirements.txt
new file mode 100644
index 0000000..0410a74
--- /dev/null
+++ b/edge-monitoring/requirements.txt
@@ -0,0 +1,4 @@
+flask
+requests
+git+git://github.com/sch3m4/pyadb@master#egg=pyadb
+python-daemon