iotronic-lightning-rod/iotronic_lightningrod/modules/rest_manager.py

717 lines
27 KiB
Python

# Copyright 2017 MDSLAB - University of Messina
# All Rights Reserved.
#
# 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.
__author__ = "Nicola Peditto <n.peditto@gmail.com>"
from iotronic_lightningrod.common.pam import pamAuthentication
from iotronic_lightningrod.common import utils
from iotronic_lightningrod.lightningrod import board
from iotronic_lightningrod.lightningrod import iotronic_status
from iotronic_lightningrod.modules import device_manager
from iotronic_lightningrod.modules import Module
from iotronic_lightningrod.modules import service_manager
from iotronic_lightningrod.modules import utils as lr_utils
from datetime import datetime
from flask import Flask
from flask import redirect
from flask import render_template
from flask import request
from flask import send_file
from flask import session as f_session
from flask import url_for
from flask import abort
# from flask import Response
import os
import subprocess
import threading
from oslo_config import cfg
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class RestManager(Module.Module):
def __init__(self, board, session=None):
super(RestManager, self).__init__("RestManager", board)
def finalize(self):
threading.Thread(target=self._runRestServer, args=()).start()
def restore(self):
pass
def _runRestServer(self):
APP_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TEMPLATE_PATH = os.path.join(APP_PATH, 'modules/web/templates/')
STATIC_PATH = os.path.join(APP_PATH, 'modules/web/static/')
app = Flask(
__name__,
template_folder=TEMPLATE_PATH,
static_folder=STATIC_PATH,
static_url_path="/static"
)
app.secret_key = os.urandom(24).hex() # to use flask session
UPLOAD_FOLDER = '/tmp'
ALLOWED_EXTENSIONS = set(['tar.gz', 'gz'])
ALLOWED_STTINGS_EXTENSIONS = set(['json'])
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
@app.route('/')
def home():
if 'username' in f_session:
return render_template('home.html')
else:
return render_template('login.html')
def redirect_dest(fallback):
dest = request.args.get('next')
try:
dest_url = url_for(dest)
except Exception:
return redirect(fallback)
return redirect(dest_url)
@app.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
if pamAuthentication(
str(request.form['username']),
str(request.form['password'])
):
f_session['username'] = request.form['username']
return redirect_dest(fallback="/")
else:
error = 'Invalid Credentials. Please try again.'
if 'username' in f_session:
return render_template('home.html')
else:
return render_template('login.html', error=error)
@app.route('/logout')
def logout():
# remove the username from the session if it's there
f_session.pop('username', None)
return redirect("/login", code=302)
@app.route('/info')
def info():
wstun_status = service_manager.wstun_status()
if wstun_status == 0:
wstun_status = "Online"
else:
wstun_status = "Offline"
service_list = service_manager.services_list("list")
if service_list == "":
service_list = "no services exposed!"
lr_cty = "N/A"
from iotronic_lightningrod.lightningrod import wport
sock_bundle = lr_utils.get_socket_info(wport)
if sock_bundle != "N/A":
lr_cty = sock_bundle[2] + " - " + sock_bundle[0] \
+ " - " + sock_bundle[1]
webservice_list = []
nginx_path = "/etc/nginx/conf.d/"
if os.path.exists(nginx_path):
active_webservice_list = [f for f in os.listdir(nginx_path)
if os.path.isfile(os.path.join(nginx_path, f))]
if len(active_webservice_list) != 0:
for ws in active_webservice_list:
ws = ws.replace('.conf', '')
webservice_list.append(ws)
info = {
'board_id': board.uuid,
'board_name': board.name,
'wagent': board.agent,
'session_id': board.session_id,
'timestamp': str(
datetime.now().strftime('%Y-%m-%dT%H:%M:%S.%f')),
'wstun_status': wstun_status,
'board_reg_status': str(board.status),
'iotronic_status': str(iotronic_status(board.status)),
'service_list': service_list,
'webservice_list': webservice_list,
'serial_dev': device_manager.getSerialDevice(),
'nic': lr_cty,
'lr_version': str(
utils.get_version("iotronic-lightningrod")
)
}
return info, 200
@app.route('/status')
def status():
try:
if ('username' in f_session):
f_session['status'] = str(board.status)
wstun_status = service_manager.wstun_status()
if wstun_status == 0:
wstun_status = "Online"
else:
wstun_status = "Offline"
service_list = service_manager.services_list("html")
if service_list == "":
service_list = "no services exposed!"
webservice_list = ""
nginx_path = "/etc/nginx/conf.d/"
if os.path.exists(nginx_path):
active_webservice_list = [
f for f in os.listdir(nginx_path)
if os.path.isfile(os.path.join(nginx_path, f))
]
for ws in active_webservice_list:
ws = ws.replace('.conf', '')[3:]
webservice_list = webservice_list + "\
<li>" + ws + "</li>"
else:
webservice_list = "no webservices exposed!"
if webservice_list == "":
webservice_list = "no webservices exposed!"
lr_cty = "N/A"
from iotronic_lightningrod.lightningrod import wport
sock_bundle = lr_utils.get_socket_info(wport)
if sock_bundle != "N/A":
lr_cty = sock_bundle[2] + " - " + sock_bundle[0] \
+ " - " + sock_bundle[1]
info = {
'board_id': board.uuid,
'board_name': board.name,
'wagent': board.agent,
'session_id': board.session_id,
'timestamp': str(
datetime.now().strftime('%Y-%m-%dT%H:%M:%S.%f')),
'wstun_status': wstun_status,
'board_reg_status': str(board.status),
'iotronic_status': str(iotronic_status(board.status)),
'service_list': str(service_list),
'webservice_list': str(webservice_list),
'serial_dev': device_manager.getSerialDevice(),
'nic': lr_cty,
'lr_version': str(
utils.get_version("iotronic-lightningrod")
)
}
return render_template('status.html', **info)
else:
return redirect(url_for('login', next=request.endpoint))
except Exception as err:
LOG.error(err)
info = {
'messages': [str(err)]
}
return render_template('status.html', **info)
@app.route('/system')
def system():
if 'username' in f_session:
info = {
'board_status': board.status
}
return render_template('system.html', **info)
else:
return redirect(url_for('login', next=request.endpoint))
@app.route('/network')
def network():
if 'username' in f_session:
info = {
'ifconfig': device_manager.getIfconfig()
}
return render_template('network.html', **info)
else:
return redirect(url_for('login', next=request.endpoint))
def lr_config(ragent, code):
bashCommand = "lr_configure %s %s " % (code, ragent)
process = subprocess.Popen(bashCommand.split(),
stdout=subprocess.PIPE)
output, error = process.communicate()
return
def change_hostname(hostname):
if hostname != "":
bashCommand = "hostname %s " % (hostname)
process = subprocess.Popen(bashCommand.split(),
stdout=subprocess.PIPE)
output, error = process.communicate()
else:
print("- No hostname specified!")
return
def lr_install():
bashCommand = "lr_install"
process = subprocess.Popen(bashCommand.split(),
stdout=subprocess.PIPE)
output, error = process.communicate()
return
def identity_backup():
bashCommand = "device_bkp_rest backup --path /tmp "\
+ "| grep filename: |awk '{print $4}'"
process = subprocess.Popen(bashCommand,
stdout=subprocess.PIPE, shell=True)
output, error = process.communicate()
return output.decode('ascii').strip()
def identity_restore(filepath):
bashCommand = "device_bkp_rest restore " \
+ str(filepath) + "| tail -n 1"
process = subprocess.Popen(bashCommand,
stdout=subprocess.PIPE, shell=True)
output, error = process.communicate()
return output.decode('ascii').strip()
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
def allowed_settings(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() \
in ALLOWED_STTINGS_EXTENSIONS
@app.route('/restore', methods=['GET', 'POST'])
def upload_file():
if ('username' in f_session) or str(board.status) == "first_boot":
f_session['status'] = str(board.status)
if request.form.get('dev_rst_btn') == 'Device restore':
if 'rst_file' not in request.files:
error = 'Identity restore result: No file uploaded!'
print(" - " + error)
info = {
'board_status': board.status
}
return render_template(
'config.html',
**info,
error=error
)
else:
file = request.files['rst_file']
if file.filename == '':
error = 'Identity restore result: No filename!'
print(" - " + error)
info = {
'board_status': board.status
}
return render_template('config.html', **info,
error=error)
else:
filename = file.filename
print("Identity file uploaded: " + str(filename))
if file and allowed_file(file.filename):
bpath = os.path.join(
app.config['UPLOAD_FOLDER'],
filename
)
bpath = bpath.replace(" ", "")
bpath = bpath.replace("(", "-")
bpath = bpath.replace(")", "-")
print("--> storage path: " + str(bpath))
file.save(bpath)
out_res = identity_restore(bpath)
print("--> restore result: " + str(out_res))
# restart LR
print("--> LR restarting in 5 seconds...")
f_session['status'] = "restarting"
lr_utils.LR_restart_delayed(5)
return redirect("/", code=302)
else:
error = 'Identity restore result: ' \
+ 'file extention not allowed!'
print(" - " + error)
info = {
'board_status': board.status
}
return render_template(
'config.html',
**info,
error=error
)
# return redirect("/config", code=302)
else:
return redirect("/", code=302)
else:
return redirect(url_for('login', next=request.endpoint))
@app.route('/backup', methods=['GET'])
def backup_download():
# LOG.info(request.query_string)
# LOG.info(request.__dict__)
if 'username' in f_session:
print("Identity file downloading: ")
filename = identity_backup()
print("--> backup created:" + str(filename))
path = str(filename)
if path is None:
print("Error path None")
try:
print("--> backup file sent.")
return send_file(path, as_attachment=True)
except Exception as e:
print(e)
else:
return redirect(url_for('login', next=request.endpoint))
@app.route('/factory', methods=['GET'])
def factory_reset():
if 'username' in f_session:
print("Lightning-rod factory reset: ")
f_session['status'] = str(board.status)
# delete nginx conf.d files
os.system("rm /etc/nginx/conf.d/lr_*")
print("--> NGINX settings deleted.")
# delete letsencrypt
os.system("rm -r /etc/letsencrypt/*")
print("--> LetsEncrypt settings deleted.")
# delete var-iotronic
os.system("rm -r /var/lib/iotronic/*")
print("--> Iotronic data deleted.")
# delete etc-iotronic
os.system("rm -r /etc/iotronic/*")
print("--> Iotronic settings deleted.")
# exec lr_install
lr_install()
# restart LR
print("--> LR restarting in 5 seconds...")
f_session['status'] = "restarting"
lr_utils.LR_restart_delayed(5)
return redirect("/", code=302)
else:
return redirect(url_for('login', next=request.endpoint))
@app.route('/config', methods=['GET', 'POST'])
def config():
if ('username' in f_session) or str(board.status) == "first_boot":
f_session['status'] = str(board.status)
if request.method == 'POST':
req_body = request.get_json()
LOG.debug(req_body)
if req_body != None:
if 'action' in req_body:
if req_body['action'] == "configure":
LOG.info("API LR configuration")
ragent = req_body['urlwagent']
code = req_body['code']
lr_config(ragent, code)
if 'hostname' in req_body:
if req_body['hostname'] != "":
change_hostname(req_body['hostname'])
return {"result": "LR configured, \
authenticating..."}, 200
else:
abort(400)
elif request.form.get('reg_btn') == 'CONFIGURE':
ragent = request.form['urlwagent']
code = request.form['code']
lr_config(ragent, code)
hostname = request.form['hostname']
change_hostname(hostname)
return redirect("/status", code=302)
elif request.form.get('rst_btn') == 'RESTORE':
utils.restoreConf()
print("Restored")
f_session['status'] = "restarting"
return redirect("/", code=302)
elif request.form.get('fct_btn'):
utils.restoreFactoryConf()
print("Refactored")
print("--> LR restarting in 5 seconds...")
f_session['status'] = "restarting"
lr_utils.LR_restart_delayed(5)
return redirect("/", code=302)
elif request.form.get('change_hostname'):
hostname = request.form['hostname']
change_hostname(hostname)
return redirect("/system", code=302)
elif request.form.get('rst_settings_btn'):
print("Settings restoring from uploaded backup...")
if len(request.files) != 0:
if 'rst_settings_file' in request.files:
file = request.files['rst_settings_file']
if file.filename == '':
error = 'Settings restore result: ' \
+ 'No filename!'
print(" - " + error)
info = {
'board_status': board.status
}
return render_template(
'config.html',
**info,
error=error
)
else:
filename = file.filename
print(" - file uploaded: " + str(filename))
if file and allowed_settings(filename):
bpath = os.path.join(
app.config['UPLOAD_FOLDER'],
filename
)
file.save(bpath)
try:
os.system(
'cp '
+ bpath
+ ' /etc/iotronic/'
+ 'settings.json'
)
except Exception as e:
LOG.warning(
"Error restoring " +
"configuration " + str(e))
print(" - done!")
if board.status == "first_boot":
# start LR
print(" - LR starting "
+ "in 5 seconds...")
f_session['status'] = "starting"
else:
# restart LR
print(" - LR restarting "
+ "in 5 seconds...")
f_session['status'] = "restarting"
lr_utils.LR_restart_delayed(5)
return redirect("/", code=302)
else:
error = 'Wrong file extention: ' \
+ str(filename)
print(" - " + error)
info = {
'board_status': board.status
}
return render_template(
'config.html',
**info,
error=error
)
else:
error = 'input form error!'
print(" - " + error)
info = {
'board_status': board.status
}
return render_template('config.html', **info,
error=error)
else:
error = "no settings file specified!"
print(" - " + error)
info = {
'board_status': board.status
}
return render_template(
'config.html',
**info,
error=error
)
return redirect("/config", code=302)
else:
print("Error POST request")
return redirect("/status", code=302)
else:
if board.status == "first_boot":
urlwagent = request.args.get('urlwagent') or ""
code = request.args.get('code') or ""
info = {
'urlwagent': urlwagent,
'code': code,
'board_status': board.status
}
return render_template('config.html', **info)
else:
if request.args.get('bkp_btn'):
# utils.backupConf()
print("Settings file downloading: ")
path = "/etc/iotronic/settings.json"
if path is None:
print("Error path None")
return redirect("/config", code=500)
try:
fn_download = "settings_" + str(
datetime.now().strftime(
'%Y-%m-%dT%H:%M:%S.%f')) + ".json"
print("--> backup settings file sent.")
return send_file(
path,
as_attachment=True,
attachment_filename=fn_download
)
except Exception as e:
print(e)
return redirect("/config", code=500)
elif request.args.get('rst_btn'):
utils.restoreConf()
print("Restored")
return redirect("/config", code=302)
elif request.args.get('fct_btn'):
utils.restoreFactoryConf()
print("Refactored")
print("--> LR restarting in 5 seconds...")
f_session['status'] = "restarting"
lr_utils.LR_restart_delayed(5)
return redirect("/", code=302)
elif request.args.get('lr_restart_btn'):
print("LR restarting in 5 seconds...")
f_session['status'] = "restarting"
lr_utils.LR_restart_delayed(5)
return redirect("/", code=302)
else:
info = {
'board_status': board.status
}
return render_template('config.html', **info)
else:
if request.method == 'POST':
req_body = request.get_json()
if req_body != None and str(board.status) != "first_boot":
return {"result": "LR already configured!"}, 403
return redirect(url_for('login', next=request.endpoint))
app.run(host='0.0.0.0', port=1474, debug=False, use_reloader=False)