Release 0.5.0:

- added support for wstun allowlist
- fixed nginx proxy
- added GDB management patch
- added DeviceFactoryReset RPC
- added action to force certificates renewing
- added ServicesStatus RPC
- added support Arancino device layout
- updated info API and status page: webservices list added
- fixed deploy scripts
- updated UI
- fixed rest_manager
- updated plugin_manager
- added checks in modules loader
- fixed device_bkp_rest script (restore function)
- rest submit action added
- updated info and status APIs
- Zuul Openstack CI configuration upgraded

Change-Id: I7d5398c2eb8c5d759f2488166a4016c5fcad35d1
This commit is contained in:
Nicola Peditto 2021-11-03 10:27:56 +01:00
parent f5fa5454dd
commit 47d682fff5
23 changed files with 918 additions and 177 deletions

View File

@ -77,6 +77,7 @@ class PamResp(Structure):
def __repr__(self):
return "<PamResp %i '%s'>" % (self.resp_retcode, self.resp)
conv_func = CFUNCTYPE(
c_int,
c_int,
@ -90,6 +91,7 @@ class PamWrapper(Structure):
"""pam_conv structure wrapper"""
_fields_ = [("conv", conv_func), ("appdata_ptr", c_void_p)]
pamLib_start = libpam.pam_start
pamLib_start.restype = c_int
pamLib_start.argtypes = [

View File

@ -0,0 +1,56 @@
# Copyright 2011 OpenStack Foundation
# 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>"
import inspect
from iotronic_lightningrod.devices import Device
from iotronic_lightningrod.devices.gpio import arancino
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
def whoami():
return inspect.stack()[1][3]
def makeNothing():
pass
class System(Device.Device):
def __init__(self):
super(System, self).__init__("arancino")
arancino.ArancinoGpio().EnableGPIO()
def finalize(self):
"""Function called at the end of module loading (after RPC registration).
:return:
"""
pass
async def testRPC(self):
rpc_name = whoami()
LOG.info("RPC " + rpc_name + " CALLED...")
await makeNothing()
result = " - " + rpc_name + " result: testRPC is working!!!\n"
LOG.info(result)
return result

View File

@ -0,0 +1,37 @@
# Copyright 2011 OpenStack Foundation
# 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 oslo_log import log as logging
from iotronic_lightningrod.devices.gpio import Gpio
LOG = logging.getLogger(__name__)
class ArancinoGpio(Gpio.Gpio):
def __init__(self):
super(ArancinoGpio, self).__init__("arancino")
LOG.info("Arancino GPIO module importing...")
# Enable GPIO
def EnableGPIO(self):
result = " - GPIO not available for 'arancino' device!"
LOG.info(result)
def DisableGPIO(self):
result = " - GPIO not available for 'arancino' device!"
LOG.info(result)

View File

@ -65,8 +65,9 @@ class System(Device.Device):
"""
LOG.info(" - readVoltage CALLED... reading pin " + Apin)
voltage = self.gpio._readVoltage(Apin)
voltage = await self.gpio._readVoltage(Apin)
result = "read voltage for " + Apin + " pin: " + voltage
result = await "read voltage for " + Apin + " pin: " + voltage
LOG.info(result)
return result

View File

@ -26,7 +26,7 @@ from oslo_log import log as logging
import asyncio
import inspect
import os
import pkg_resources
# import pkg_resources
import signal
import ssl
import sys
@ -286,13 +286,22 @@ def iotronic_status(board_status):
wamp_singleCheck(SESSION),
loop
)
alive = alive.result()
try:
alive = alive.result(timeout=5)
except asyncio.TimeoutError:
LOG.warning('Check Iotronic request timeout')
alive.cancel()
alive = "alive_req_canceled"
except asyncio.TimeoutError as e:
LOG.error(" - Iotronic check timeout: " + str(e))
alive = "rpc_timeout"
except Exception as e:
LOG.error(" - Iotronic check: " + str(e))
alive = e
alive = "not_connected"
else:
alive = "Not connected!"
alive = "not_connected"
return alive
@ -445,6 +454,9 @@ async def IotronicLogin(board, session, details):
# reconnection = False
else:
LOG.warning(
" - " + str(w_msg.result) + ": " + str(w_msg.message)
)
Bye()
except exception.ApplicationError as e:
@ -819,8 +831,10 @@ def wampConnect(wamp_conf):
async def onConnectFailure(session, fail_msg):
LOG.warning("WAMP Connection Failure: " + str(fail_msg))
"""
LOG.warning(" - timeout set @ " +
str(CONF.autobahn.connection_failure_timer))
"""
global connFailure
if connFailure != None:
@ -930,9 +944,9 @@ def wampConnect(wamp_conf):
LOG.error("Reconnection wrong status!")
except IndexError as err:
LOG.error(" - Error parsing WAMP url: " + str(err))
LOG.error(" --> port or address not specified")
board.status = "url_wamp_error"
LOG.error(" - Error parsing WAMP url: " + str(err))
LOG.error(" --> port or address not specified")
board.status = "url_wamp_error"
except Exception as err:
LOG.error(" - WAMP connection error: " + str(err))
@ -1026,68 +1040,73 @@ def modulesLoader(session):
"""
LOG.info("Available modules: ")
try:
ep = []
LOG.info("Available modules: ")
for ep in pkg_resources.iter_entry_points(group='s4t.modules'):
LOG.info(" - " + str(ep))
ep = []
if not ep:
for ep in pkg_resources.iter_entry_points(group='s4t.modules'):
LOG.info(" - " + str(ep))
LOG.info("No modules available!")
sys.exit()
if not ep:
else:
LOG.info("No modules available!")
sys.exit()
modules = extension.ExtensionManager(
namespace='s4t.modules',
# invoke_on_load=True,
# invoke_args=(session,),
)
else:
LOG.info('Modules to load:')
modules = extension.ExtensionManager(
namespace='s4t.modules',
# invoke_on_load=True,
# invoke_args=(session,),
)
for ext in modules.extensions:
LOG.info('Modules to load:')
LOG.debug(ext.name)
for ext in modules.extensions:
if (ext.name == 'gpio') & (board.type == 'server'):
LOG.info("- GPIO module disabled for 'server' devices")
LOG.debug(ext.name)
else:
if (ext.name == 'gpio') & (board.type == 'server'):
LOG.info("- GPIO module disabled for 'server' devices")
if ext.name != "rest":
else:
mod = ext.plugin(board, session)
if ext.name != "rest":
global MODULES
MODULES[mod.name] = mod
mod = ext.plugin(board, session)
# Methods list for each module
meth_list = inspect.getmembers(
mod, predicate=inspect.ismethod
)
global MODULES
MODULES[mod.name] = mod
global RPC
RPC[mod.name] = meth_list
# Methods list for each module
meth_list = inspect.getmembers(
mod, predicate=inspect.ismethod
)
if len(meth_list) == 3:
# there are at least two methods for each module:
# "__init__" and "finalize"
global RPC
RPC[mod.name] = meth_list
LOG.info(" - No RPC to register for "
+ str(ext.name) + " module!")
if len(meth_list) == 3:
# there are at least two methods for each module:
# "__init__" and "finalize"
else:
LOG.info(" - RPC list of " + str(mod.name) + ":")
moduleWampRegister(SESSION, meth_list)
LOG.info(" - No RPC to register for "
+ str(ext.name) + " module!")
# Call the finalize procedure for each module
mod.finalize()
else:
LOG.info(" - RPC list of " + str(mod.name) + ":")
moduleWampRegister(SESSION, meth_list)
LOG.info("Lightning-rod modules loaded.")
LOG.info("\n\nListening...")
# Call the finalize procedure for each module
mod.finalize()
except Exception as err:
LOG.warning("Board modules loading error: " + str(err))
LOG.info("Lightning-rod modules loaded.")
LOG.info("\n\nListening...")
def moduleReloadInfo(session):

View File

@ -21,11 +21,14 @@ import os
import subprocess
import threading
import time
import json
import requests
from autobahn.wamp import exception
from datetime import datetime
from iotronic_lightningrod.common import utils
# from iotronic_lightningrod.common.exception import timeout
from iotronic_lightningrod.config import package_path
from iotronic_lightningrod.lightningrod import RPC_devices
from iotronic_lightningrod.lightningrod import wampNotify
@ -495,7 +498,8 @@ class DeviceManager(Module.Module):
+ " " + str(pkg)
else:
command = command + " " + str(cmd) + " " + str(pkg)
command = command + " " + str(cmd) \
+ " " + str(pkg)
if 'version' in parameters:
@ -682,6 +686,41 @@ class DeviceManager(Module.Module):
return w_msg.serialize()
# SC
async def DeviceFactoryReset(self, req, parameters=None):
req_id = req['uuid']
rpc_name = utils.getFuncName()
LOG.info("RPC " + rpc_name + " CALLED [req_id: " + str(req_id) + "]:")
if parameters is not None:
LOG.info(" - " + rpc_name + " parameters: " + str(parameters))
def FactoryReset():
message = factory_reset()
w_msg = WM.WampSuccess(msg=message, req_id=req_id)
if (req['main_request_uuid'] != None):
wampNotify(self.device_session,
self.board, w_msg.serialize(), rpc_name)
else:
return w_msg
if (req['main_request_uuid'] != None):
LOG.info(" - main request: " + str(req['main_request_uuid']))
try:
threading.Thread(target=FactoryReset).start()
w_msg = WM.WampRunning(msg=rpc_name, req_id=req_id)
except Exception as err:
message = "Error in thr_" + rpc_name + ": " + str(err)
LOG.error(message)
w_msg = WM.WampError(msg=message, req_id=req_id)
else:
w_msg = FactoryReset()
return w_msg.serialize()
# SC
async def DeviceNetConfig(self, req, parameters=None):
req_id = req['uuid']
@ -717,6 +756,136 @@ class DeviceManager(Module.Module):
return w_msg.serialize()
# SC
async def DeviceRestSubmit(self, req, parameters=None):
req_id = req['uuid']
rpc_name = utils.getFuncName()
LOG.info("RPC " + rpc_name + " CALLED [req_id: " + str(req_id) + "]:")
if parameters is not None:
LOG.info(" - " + rpc_name + " parameters: " + str(parameters))
def RestSubmit():
try:
if 'url' in parameters:
url = str(parameters['url'])
else:
message = "Error RestSubmit: no url specified."
LOG.error(message)
w_msg = WM.WampError(msg=message, req_id=req_id)
return w_msg
if 'method' in parameters:
method = str(parameters['method'])
else:
message = "Error RestSubmit: no REST method specified."
LOG.error(message)
w_msg = WM.WampError(msg=message, req_id=req_id)
return w_msg
response = requests.request(
method,
url,
params=json.dumps(parameters['params']) if '\
params' in parameters else None,
data=json.dumps(parameters['data']) if '\
data' in parameters else None,
json=parameters['json'] if '\
json' in parameters else None,
headers=parameters['headers'] if '\
headers' in parameters else None,
cookies=parameters['cookies'] if '\
cookies' in parameters else None,
files=parameters['files'] if '\
files' in parameters else None,
auth=parameters['auth'] if '\
auth' in parameters else None,
timeout=float(parameters['timeout']) if '\
timeout' in parameters else None,
allow_redirects=parameters['allow_redirects'] if '\
allow_redirects' in parameters else True,
proxies=parameters['proxies'] if '\
proxies' in parameters else None,
verify=parameters['verify'] if '\
verify' in parameters else True,
stream=parameters['stream'] if '\
stream' in parameters else False,
cert=parameters['cert'] if '\
cert' in parameters else None,
)
res = json.loads(response.text)
w_msg = WM.WampSuccess(msg=res, req_id=req_id)
except Exception as err:
return WM.WampError(msg=str(err), req_id=req_id)
if (req['main_request_uuid'] != None):
wampNotify(self.device_session,
self.board, w_msg.serialize(), rpc_name)
else:
return w_msg
if (req['main_request_uuid'] != None):
LOG.info(" - main request: " + str(req['main_request_uuid']))
try:
threading.Thread(target=RestSubmit).start()
w_msg = WM.WampRunning(msg=rpc_name, req_id=req_id)
except Exception as err:
message = "Error in thr_" + rpc_name + ": " + str(err)
LOG.error(message)
w_msg = WM.WampError(msg=message, req_id=req_id)
else:
w_msg = RestSubmit()
return w_msg.serialize()
def lr_install():
bashCommand = "lr_install"
process = subprocess.Popen(bashCommand.split(),
stdout=subprocess.PIPE)
output, error = process.communicate()
return
def factory_reset():
LOG.info("Lightning-rod factory reset: ")
# delete nginx conf.d files
os.system("rm /etc/nginx/conf.d/lr_*")
LOG.info("--> NGINX settings deleted.")
# delete letsencrypt
os.system("rm -r /etc/letsencrypt/*")
LOG.info("--> LetsEncrypt settings deleted.")
# delete var-iotronic
os.system("rm -r /var/lib/iotronic/*")
LOG.info("--> Iotronic data deleted.")
# delete etc-iotronic
os.system("rm -r /etc/iotronic/*")
LOG.info("--> Iotronic settings deleted.")
# exec lr_install
lr_install()
# restart LR
LOG.info("--> LR restarting in 5 seconds...")
lr_utils.LR_restart_delayed(5)
return "Device reset completed"
def getIfconfig():

View File

@ -25,6 +25,7 @@ import queue
import shutil
import threading
import time
import platform
from iotronic_lightningrod.common import utils
@ -149,9 +150,17 @@ class PluginManager(Module.Module):
try:
if (plugin_uuid in PLUGINS_THRS) and (
PLUGINS_THRS[plugin_uuid].isAlive()
):
worker_alive = False
if (plugin_uuid in PLUGINS_THRS):
worker = PLUGINS_THRS[plugin_uuid]
pyvers = platform.python_version_tuple()
if int(pyvers[0]) == 3 and int(pyvers[1]) >= 9:
worker_alive = worker.is_alive()
else:
worker_alive = worker.isAlive()
if (plugin_uuid in PLUGINS_THRS) and worker_alive:
LOG.warning(" - Plugin "
+ plugin_uuid + " already started!")
@ -380,10 +389,19 @@ class PluginManager(Module.Module):
plugin_name = plugins_conf['plugins'][plugin_uuid]['name']
worker_alive = False
if (plugin_uuid in PLUGINS_THRS):
worker = PLUGINS_THRS[plugin_uuid]
pyvers = platform.python_version_tuple()
if int(pyvers[0]) == 3 and int(pyvers[1]) >= 9:
worker_alive = worker.is_alive()
else:
worker_alive = worker.isAlive()
# Check if the plugin is already running
if (plugin_uuid in PLUGINS_THRS) and (
PLUGINS_THRS[plugin_uuid].isAlive()
):
if (plugin_uuid in PLUGINS_THRS) and worker_alive:
message = "ALREADY STARTED!"
LOG.warning(" - Plugin "
@ -495,10 +513,19 @@ class PluginManager(Module.Module):
if plugin_uuid in PLUGINS_THRS:
worker = PLUGINS_THRS[plugin_uuid]
LOG.debug(" - Stopping plugin " + str(worker))
worker_alive = False
if (plugin_uuid in PLUGINS_THRS):
worker = PLUGINS_THRS[plugin_uuid]
LOG.debug(" - Stopping plugin " + str(worker))
if worker.isAlive():
pyvers = platform.python_version_tuple()
if int(pyvers[0]) == 3 and int(pyvers[1]) >= 9:
worker_alive = worker.is_alive()
else:
worker_alive = worker.isAlive()
if worker_alive:
if 'delay' in parameters:
time.sleep(delay)
@ -556,9 +583,17 @@ class PluginManager(Module.Module):
try:
if (plugin_uuid in PLUGINS_THRS) and (
PLUGINS_THRS[plugin_uuid].isAlive()
):
worker_alive = False
if (plugin_uuid in PLUGINS_THRS):
worker = PLUGINS_THRS[plugin_uuid]
pyvers = platform.python_version_tuple()
if int(pyvers[0]) == 3 and int(pyvers[1]) >= 9:
worker_alive = worker.is_alive()
else:
worker_alive = worker.isAlive()
if (plugin_uuid in PLUGINS_THRS) and worker_alive:
message = "Plugin " + plugin_uuid + " already started!"
LOG.warning(" - " + message)
@ -727,7 +762,14 @@ class PluginManager(Module.Module):
if plugin_uuid in PLUGINS_THRS:
worker = PLUGINS_THRS[plugin_uuid]
if worker.isAlive():
pyvers = platform.python_version_tuple()
if int(pyvers[0]) == 3 and int(pyvers[1]) >= 9:
worker_alive = worker.is_alive()
else:
worker_alive = worker.isAlive()
if worker_alive:
LOG.info(" - Plugin '"
+ plugin_name + "' is running...")
worker.stop()
@ -798,17 +840,28 @@ class PluginManager(Module.Module):
worker = PLUGINS_THRS[plugin_uuid]
pyvers = platform.python_version_tuple()
if int(pyvers[0]) == 3 and int(pyvers[1]) >= 9:
worker_alive = worker.is_alive()
else:
worker_alive = worker.isAlive()
# STOP PLUGIN----------------------------------------------
if worker.isAlive():
if worker_alive:
LOG.info(" - Thread "
+ plugin_uuid + " is running, stopping...")
LOG.debug(" - Stopping plugin " + str(worker))
worker.stop()
while worker.isAlive():
pass
if int(pyvers[0]) == 3 and int(pyvers[1]) >= 9:
worker_alive = worker.is_alive()
while worker.is_alive():
pass
else:
while worker.isAlive():
pass
# Remove from plugin thread list
del PLUGINS_THRS[plugin_uuid]
@ -893,7 +946,13 @@ class PluginManager(Module.Module):
worker = PLUGINS_THRS[plugin_uuid]
if worker.isAlive():
pyvers = platform.python_version_tuple()
if int(pyvers[0]) == 3 and int(pyvers[1]) >= 9:
worker_alive = worker.is_alive()
else:
worker_alive = worker.isAlive()
if worker_alive:
result = "ALIVE"
else:
result = "DEAD"

View File

@ -24,7 +24,6 @@ LOG = logging.getLogger(__name__)
import json
import os
import shutil
import subprocess
import time
@ -212,6 +211,16 @@ class ProxyManager(Proxy.Proxy):
nginx_board_conf = '''server {{
listen 50000;
server_name {0};
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
location / {{
proxy_pass http://127.0.0.1:1474;
}}
@ -294,6 +303,39 @@ class ProxyManager(Proxy.Proxy):
return json.dumps(nginxMsg)
def _proxyRenewWebservice(self):
nginxMsg = {}
try:
command = "/usr/bin/certbot " \
"renew " \
"--force-renewal " \
"-w /var/www/html"
LOG.info("Certbot is renewing certificate:")
LOG.info(command)
certbot_result = call(command, shell=True)
LOG.info("CERTBOT RESULT: " + str(certbot_result))
if (certbot_result == 0):
nginxMsg['result'] = "SUCCESS"
nginxMsg['message'] = "Webservice certificate renewed."
else:
nginxMsg['result'] = "ERROR"
nginxMsg['message'] = "Renewing certificate failure."
LOG.info("--> " + nginxMsg['message'])
except Exception as err:
nginxMsg['log'] = "Renewing certificate error: " + str(err)
nginxMsg['code'] = ""
LOG.warning("--> " + nginxMsg['log'])
return json.dumps(nginxMsg)
def _exposeWebservice(self, board_dns, service_dns, local_port, dns_list):
nginxMsg = {}
@ -316,7 +358,7 @@ class ProxyManager(Proxy.Proxy):
proxy_set_header Connection "upgrade";
location / {{
proxy_pass http://localhost:{1};
proxy_pass http://127.0.0.1:{1};
}}
location ~ /.well-known {{
root /var/www/html;

View File

@ -34,8 +34,9 @@ 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 getpass
import os
import subprocess
import threading
@ -144,6 +145,18 @@ class RestManager(Module.Module):
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,
@ -155,6 +168,7 @@ class RestManager(Module.Module):
'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(
@ -162,56 +176,85 @@ class RestManager(Module.Module):
)
}
return info
return info, 200
@app.route('/status')
def status():
if ('username' in f_session):
try:
f_session['status'] = str(board.status)
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)
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!"
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]
return redirect(url_for('login', next=request.endpoint))
except Exception as err:
LOG.error(err)
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),
'serial_dev': device_manager.getSerialDevice(),
'nic': lr_cty,
'lr_version': str(
utils.get_version("iotronic-lightningrod")
)
}
'messages': [str(err)]
}
return render_template('status.html', **info)
else:
return redirect(url_for('login', next=request.endpoint))
@app.route('/system')
def system():
if 'username' in f_session:
@ -360,7 +403,7 @@ class RestManager(Module.Module):
**info,
error=error
)
return redirect("/config", code=302)
# return redirect("/config", code=302)
else:
return redirect("/", code=302)
@ -370,6 +413,9 @@ class RestManager(Module.Module):
@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: ")
@ -433,7 +479,33 @@ class RestManager(Module.Module):
if request.method == 'POST':
if request.form.get('reg_btn') == 'CONFIGURE':
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)
@ -633,6 +705,12 @@ class RestManager(Module.Module):
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)

View File

@ -1,5 +1,4 @@
# Copyright 2017 MDSLAB - University of Messina
# All Rights Reserved.
# 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
@ -24,9 +23,6 @@ import signal
import socket
import subprocess
import time
import threading
import copy
from datetime import datetime
from random import randint
@ -36,9 +32,7 @@ from urllib.parse import urlparse
from iotronic_lightningrod.common import utils
from iotronic_lightningrod.config import package_path
from iotronic_lightningrod.modules import Module
from iotronic_lightningrod import lightningrod
import iotronic_lightningrod.wampmessage as WM
@ -101,6 +95,8 @@ class ServiceManager(Module.Module):
if wurl_list[0] == "wss":
is_wss = True
self.board_id = board.uuid
if is_wss:
self.wstun_url = "wss://" + self.wstun_ip + ":" + self.wstun_port
else:
@ -600,7 +596,7 @@ class ServiceManager(Module.Module):
except Exception as err:
LOG.error(" --> Parsing error in " + s_conf_FILE + ": " + str(err))
if os.path.isfile(s_conf_FILE):
if os.path.isfile(s_conf_FILE + '.bkp '):
LOG.info(" --> restoring services.json file...")
@ -621,6 +617,28 @@ class ServiceManager(Module.Module):
LOG.error(" --> services.json backup file does not exist!")
s_conf = None
if s_conf == None:
try:
LOG.info(" --> loading services.json template file")
template_conf = '''{
"services": {
}
}
'''
with open(s_conf_FILE, "w") as text_file:
text_file.write("%s" % template_conf)
with open(s_conf_FILE) as settings:
s_conf = json.load(settings)
except Exception as err:
LOG.error(
" --> Loading template error: \
manual check on device required!"
)
s_conf = None
return s_conf
def _wstunMon(self, wstun, local_port):
@ -762,16 +780,24 @@ class ServiceManager(Module.Module):
try:
# subp_cmd = [CONF.services.wstun_bin, opt_reverse,
# self.wstun_url, '-u "' + str(self.board_id) + '"']
subp_cmd = [CONF.services.wstun_bin, opt_reverse,
self.wstun_url, '-u', '' + str(self.board_id) + '']
wstun = subprocess.Popen(
[CONF.services.wstun_bin, opt_reverse, self.wstun_url],
subp_cmd,
stdout=subprocess.PIPE
)
if (event != "boot"):
print("WSTUN start event:")
cmd_print = 'WSTUN exec: ' + str(CONF.services.wstun_bin) \
+ " " + opt_reverse + ' ' + self.wstun_url
# cmd_print = 'WSTUN exec: ' + str(CONF.services.wstun_bin) \
# + " " + opt_reverse + ' ' + self.wstun_url
cmd_print = 'WSTUN exec: ' + str(subp_cmd)
print(" - " + str(cmd_print))
LOG.debug(cmd_print)
@ -845,16 +871,24 @@ class ServiceManager(Module.Module):
)
try:
# subp_cmd = [CONF.services.wstun_bin, opt_reverse, self.wstun_url,
# '-u "' + str(self.board_id) + '"']
subp_cmd = [CONF.services.wstun_bin, opt_reverse, self.wstun_url,
'-u', '' + str(self.board_id) + '']
wstun = subprocess.Popen(
[CONF.services.wstun_bin, opt_reverse, self.wstun_url],
subp_cmd,
stdout=subprocess.PIPE
)
if (event != "boot"):
print("WSTUN start event:")
cmd_print = 'WSTUN exec: ' + str(CONF.services.wstun_bin) + " " \
+ opt_reverse + ' ' + self.wstun_url
# cmd_print = 'WSTUN exec: ' + str(CONF.services.wstun_bin) + " " \
# + opt_reverse + ' ' + self.wstun_url
cmd_print = 'WSTUN exec: ' + str(subp_cmd)
print(" - " + str(cmd_print))
LOG.debug(cmd_print)
@ -926,9 +960,32 @@ class ServiceManager(Module.Module):
if parameters is not None:
LOG.info(" - " + rpc_name + " parameters: " + str(parameters))
thr_list = str(threading.enumerate())
tuns = {
"sockets": [],
"procs": []
}
w_msg = WM.WampSuccess(msg=thr_list, req_id=req_id)
""" LSOF """
res_lsof = subprocess.Popen(
"lsof -i -n -P | grep '8080'| grep -v grep",
shell=True,
stdout=subprocess.PIPE
)
sockets = res_lsof.communicate()[0].decode("utf-8").split("\n")
tuns['sockets'] = sockets[:-1]
""" PS """
res_ps = subprocess.Popen(
"ps aux | grep 'wstun' | grep -v grep",
shell=True,
stdout=subprocess.PIPE
)
ps_s4t = res_ps.communicate()[0].decode("utf-8").split("\n")
tuns['procs'] = ps_s4t[:-1]
w_msg = WM.WampSuccess(msg=tuns, req_id=req_id)
return w_msg.serialize()

View File

@ -27,6 +27,8 @@ import subprocess
import sys
import threading
import time
import signal
from iotronic_lightningrod.common import utils
from iotronic_lightningrod.config import entry_points_name
@ -40,6 +42,9 @@ LOG = logging.getLogger(__name__)
global connFailureRecovery
connFailureRecovery = None
global gdbPid
gdbPid = None
class Utility(Module.Module):
@ -200,18 +205,35 @@ def destroyWampSocket():
LOG.warning("WAMP Connection Recovery timer: EXPIRED")
lr_utils.LR_restart()
def timeoutGDB():
LOG.warning("WAMP Connection Recovery GDB timer: EXPIRED")
global gdbPid
os.kill(gdbPid, signal.SIGKILL)
LOG.warning("WAMP Connection Recovery GDB process: KILLED")
LOG.warning("WAMP Connection Recovery GDB process: LR restarting...")
lr_utils.LR_restart()
connFailureRecovery = Timer(30, timeout)
connFailureRecovery.start()
LOG.warning("WAMP Connection Recovery timer: STARTED")
try:
gdbTimeoutCheck = Timer(30, timeoutGDB)
gdbTimeoutCheck.start()
LOG.debug("WAMP Connection Recovery GDB timer: STARTED")
process = subprocess.Popen(
["gdb", "-p", str(LR_PID)],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE
)
global gdbPid
gdbPid = process.pid
proc = psutil.Process()
conn_list = proc.connections()
@ -250,6 +272,9 @@ def destroyWampSocket():
)
connFailureRecovery.cancel()
gdbTimeoutCheck.cancel()
LOG.debug("WAMP Connection Recovery GDB timer: CLEANED")
if wamp_conn_set == False:
LOG.warning("WAMP CONNECTION NOT FOUND: LR restarting...")
# In conn_list there is not the WAMP connection!

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -33,7 +33,13 @@
{% else %}
<div align="center"> </div>
<div align="center">
<br>
<img src="{{ url_for('static', filename='images/stack4thingslogo.png') }}" alt="stack4thingslogo" width="260" height="260">
</div>
{% endif %}

View File

@ -28,7 +28,10 @@
<div class="login-form">
<form action="{{ url_for('login', next=request.args.get('next')) }}" method="post">
<div class="avatar"><i class="material-icons">&#xE7FF;</i></div>
<!--<div class="avatar"><i class="material-icons">&#xE7FF;</i></div>-->
<center>
<img src="{{ url_for('static', filename='images/stack4thingslogo.png') }}" alt="stack4thingslogo" width="200" height="200">
</center>
<h4 class="modal-title">Login to Lightning-rod</h4>
<div class="form-group">
<input type="text" class="form-control" name="username" placeholder="Username" required="required" value="{{request.form.username }}">

View File

@ -6,32 +6,65 @@
{% block content %}
<div class="jumbotron">
<h2>Board info @ {{ timestamp }}</h2>
<br>
<h4> Iotronic </h4>
<li> Lightning-rod version: {{lr_version}}</li>
<li> Iotronic connection status: {{iotronic_status}}</li>
<li>Name: {{ board_name }} </li>
<li>UUID: {{ board_id }} </li>
<li>Registartion status: {{ board_reg_status }}</li>
<li>WAMP Agent: {{ wagent }} </li>
<li>Session ID: {{ session_id }} </li>
<br>
<h4> WSTUN </h4>
<li>Status: {{ wstun_status }} </li>
<li>Services: <br>
<ul>
{% autoescape false %}
{{service_list}}
{% endautoescape %}
</ul>
{% if messages %}
</li>
<br>
<h4> Hardware information </h4>
<li>Serial device: {{ serial_dev }} </li>
<li>NIC: {{ nic }} </li>
<h5><b>Error messages:</b></h5>
<center>
<div align="left" style="width:90%">
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
</div>
</center>
{% else %}
<br><br>
<h2>Board info @ {{ timestamp }}</h2>
<br>
<h4> Iotronic </h4>
<li> Lightning-rod version: {{lr_version}}</li>
<li> Iotronic connection status: {{iotronic_status}}</li>
<li>Name: {{ board_name }} </li>
<li>UUID: {{ board_id }} </li>
<li>Registartion status: {{ board_reg_status }}</li>
<li>WAMP Agent: {{ wagent }} </li>
<li>Session ID: {{ session_id }} </li>
<br>
<h4> WSTUN </h4>
<li>Status: {{ wstun_status }} </li>
<li>Services: <br>
<ul>
{% autoescape false %}
{{service_list}}
{% endautoescape %}
</ul>
</li>
<li>WebServices: <br>
<ul>
{% autoescape false %}
{{webservice_list}}
{% endautoescape %}
</ul>
</li>
<br>
<h4> Hardware information </h4>
<li>Serial device: {{ serial_dev }} </li>
<li>NIC: {{ nic }} </li>
<br><br>
{% endif %}
</div>
{% endblock %}

View File

@ -1,5 +1,4 @@
# Copyright 2017 MDSLAB - University of Messina
# All Rights Reserved.
# 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
@ -18,10 +17,12 @@ __author__ = "Nicola Peditto <n.peditto@gmail.com>"
from iotronic_lightningrod.common import utils
from iotronic_lightningrod.config import package_path
from iotronic_lightningrod.lightningrod import RPC_proxies
from iotronic_lightningrod.lightningrod import wampNotify
from iotronic_lightningrod.modules import Module
import iotronic_lightningrod.wampmessage as WM
from autobahn.wamp import exception
import threading
import importlib as imp
import inspect
import json
@ -29,6 +30,7 @@ import OpenSSL.crypto
import os
import time
from oslo_config import cfg
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
@ -248,3 +250,74 @@ class WebServiceManager(Module.Module):
w_msg = WM.WampSuccess(msg=message, req_id=req_id)
return w_msg.serialize()
"""
# LONG
async def RenewWebservice(self, req, parameters=None):
req_id = req['uuid']
rpc_name = utils.getFuncName()
LOG.info("RPC " + rpc_name + " CALLED [req_id: " + str(req_id) + "]")
if parameters is not None:
LOG.info(" - " + rpc_name + " parameters: " + str(parameters))
message = self.board.proxy._proxyRenewWebservice()
w_msg = WM.WampSuccess(msg=message, req_id=req_id)
return w_msg.serialize()
"""
# LONG
async def RenewWebservice(self, req, parameters=None):
req_id = req['uuid']
rpc_name = utils.getFuncName()
LOG.info("RPC " + rpc_name + " CALLED [req_id: " + str(req_id) + "]")
if parameters is not None:
LOG.info(" - " + rpc_name + " parameters: " + str(parameters))
def renewing():
message = self.board.proxy._proxyRenewWebservice()
try:
message = json.loads(message)
w_msg = WM.WampMessage(
message=message['message'],
result=message['result'],
req_id=req_id
)
except Exception as e:
LOG.error(e)
w_msg = WM.WampError(
msg="Error returning message",
req_id=req_id
)
try:
wampNotify(
self.session,
self.board,
w_msg.serialize(),
rpc_name
)
except exception.ApplicationError as e:
LOG.error(
" - Notify result '" + rpc_name + "' error: " + str(e)
)
try:
threading.Thread(target=renewing).start()
except Exception as err:
LOG.error("Error in renewing thread: " + str(err))
out_msg = "LR is renewing webservice certificates..."
w_msg = WM.WampRunning(msg=out_msg, req_id=req_id)
return w_msg.serialize()

View File

@ -11,7 +11,7 @@ if [ "$1" = "backup" ]; then
else
bkp_path=""
bkp_path="."
fi
@ -24,13 +24,15 @@ if [ "$1" = "backup" ]; then
tar zcvf $bkp_filename /var/lib/iotronic /etc/iotronic /etc/letsencrypt /etc/nginx/conf.d &>/dev/null
elif [ "$1" = "restore" ]; then
if [ "$#" -ne 2 ]; then
echo "You have to specify: 'restore' <BACKUP_FILE> "
echo "You have to specify: 'restore' <BACKUP_FILE_PATH> "
exit
fi
# RESTORE
echo "Restoring Iotronic configuration"
tar -xvzf $2 -C /
#tar -xvzf $2 -C /
tar -xvf $2 -C /
service nginx restart
@ -39,8 +41,13 @@ elif [ "$1" = "restore" ]; then
echo -e "\nCompleted!"
else
echo "You have to specify:"
echo " - for backup: 'backup'"
echo " - for restore: 'restore' <backup-filename-to-restore>"
exit
echo "You have to specify:"
echo " - to backup: 'backup'"
echo " - options:"
echo " --path: specify path where to save the backup file; e.g.: /tmp or /home/<USER>"
echo ""
echo " - to restore: 'restore' <BACKUP_FILE_PATH>"
exit
fi

View File

@ -18,6 +18,7 @@
import os
import sys
if len(sys.argv) == 1:
print('Arguments required:')
@ -28,6 +29,13 @@ else:
if sys.argv[1] == "-c":
test=os.system('grep -Rq "<REGISTRATION-TOKEN>" ' + sys.argv[4] + '/settings.json')
#print(test)
if test == 0:
print("Configuration status: new")
else:
print("Configuration status: configured")
if len(sys.argv) < 5:
print('Arguments required: '
+ '<REGISTRATION-TOKEN> '
@ -36,20 +44,63 @@ else:
str(sys.argv)
)
else:
os.system('sed -i "s|\\"code\\":.*|\\"code\\": \\"'
+ sys.argv[2] + '\\"|g" ' + sys.argv[4] + '/settings.json')
os.system('sed -i "s|\\"url\\":.*|\\"url\\": \\"'
+ sys.argv[3] + '\\",|g" ' + sys.argv[4] + '/settings.json')
os.system('sed -i "s|<IOTRONIC-REALM>|s4t|g" '
+ sys.argv[4] + '/settings.json')
if test == 0:
#print("Configuration status: new")
os.system('sed -i "s|\\"code\\":.*|\\"code\\": \\"'
+ sys.argv[2] + '\\"|g" ' + sys.argv[4] + '/settings.json')
print("Configuration completed")
else:
check_reg=os.system('grep -Rq "main-agent" ' + sys.argv[4] + '/settings.json')
if check_reg == 0:
#print("Configuration status: configured")
os.system('sed -i "s|\\"code\\":.*|\\"code\\": \\"'
+ sys.argv[2] + '\\",|g" ' + sys.argv[4] + '/settings.json')
else:
os.system('sed -i "s|\\"code\\":.*|\\"code\\": \\"'
+ sys.argv[2] + '\\"|g" ' + sys.argv[4] + '/settings.json')
print("Configuration overwritten")
else:
test=os.system('grep -Rq "<REGISTRATION-TOKEN>" /etc/iotronic/settings.json')
#print(test)
if test == 0:
print("Configuration status: new")
else:
print("Configuration status: configured")
if len(sys.argv) < 3:
print('Arguments required: <REGISTRATION-TOKEN> <WAMP-REG-AGENT-URL>',
str(sys.argv))
else:
os.system('sed -i "s|\\"code\\":.*|\\"code\\": \\"'
+ sys.argv[1] + '\\"|g" /etc/iotronic/settings.json')
os.system('sed -i "s|\\"url\\":.*|\\"url\\": \\"'
+ sys.argv[2] + '\\",|g" /etc/iotronic/settings.json')
+ sys.argv[2] + '\\",|g" /etc/iotronic/settings.json')
os.system('sed -i "s|<IOTRONIC-REALM>|s4t|g" /etc/iotronic/settings.json')
if test == 0:
#print("Configuration status: new")
os.system('sed -i "s|\\"code\\":.*|\\"code\\": \\"'
+ sys.argv[1] + '\\"|g" /etc/iotronic/settings.json')
print("Configuration completed")
else:
check_reg=os.system('grep -Rq "main-agent" /etc/iotronic/settings.json')
if check_reg == 0:
#print("Configuration status: configured")
os.system('sed -i "s|\\"code\\":.*|\\"code\\": \\"'
+ sys.argv[1] + '\\",|g" /etc/iotronic/settings.json')
else:
os.system('sed -i "s|\\"code\\":.*|\\"code\\": \\"'
+ sys.argv[1] + '\\"|g" /etc/iotronic/settings.json')
print("Configuration overwritten")

View File

@ -5,6 +5,8 @@ import requests
r = requests.get(url = "http://localhost:1474/info")
data = r.json()
print(json.dumps(data, indent=4, sort_keys=True))
try:
data = r.json()
print(json.dumps(data, indent=4, sort_keys=True))
except:
print(r.text)

View File

@ -101,7 +101,7 @@ print(' - logrotate configured.')
os.system('cp ' + py_dist_pack + '/iotronic_lightningrod/etc/systemd/system/'
+ 's4t-lightning-rod.service '
+ '/etc/systemd/system/lightning-rod.service')
os.chmod('/etc/systemd/system/lightning-rod.service', 0o744)
print('Lightning-rod systemd script installed.')

View File

@ -21,6 +21,7 @@ commands =
[testenv:pep8]
basepython = python3.8
#commands = /usr/local/bin/flake8 {posargs}
#commands = /usr/bin/flake8 {posargs}
commands = flake8 {posargs}
[testenv:venv]

View File

@ -15,13 +15,33 @@ RUN echo $TZ > /etc/timezone && apt-get update && apt-get install -y tzdata && r
RUN apt-get update && apt-get install -y nginx python-certbot-nginx
RUN sed -i 's/# server_names_hash_bucket_size 64;/server_names_hash_bucket_size 64;/g' /etc/nginx/nginx.conf
#RUN apt-get install -y certbot
RUN rm -rf /var/lib/apt/lists/*
ENV NODE_PATH=/usr/local/lib/node_modules
RUN apt update && apt install -y wget && wget https://deb.nodesource.com/setup_10.x
RUN chmod +x setup_10.x && ./setup_10.x
RUN apt-get install -y nodejs
RUN npm install -g --unsafe @mdslab/wstun@1.0.11 && npm cache --force clean
RUN pip3 install iotronic-lightningrod
RUN git clone -b allowlist --depth 1 https://github.com/MDSLab/wstun.git /tmp/wstun/
RUN cp /tmp/wstun/bin/wstun.js /usr/lib/node_modules/@mdslab/wstun/bin/
RUN cp -r /tmp/wstun/lib/* /usr/lib/node_modules/@mdslab/wstun/lib/
#RUN apt update
#RUN apt install -y wget && wget https://deb.nodesource.com/setup_10.x
#RUN chmod +x setup_10.x && ./setup_10.x
#RUN apt-get install -y nodejs
#RUN npm i -g npm@latest
#RUN npm install -g --unsafe websocket@1.0.26 optimist@0.6.1 node-uuid@1.4.7 under_score log4js@1.1.1 && npm cache --force clean
#RUN cp /usr/local/lib/node_modules/@mdslab/wstun/bin/wstun.js /usr/local/bin/wstun
#RUN ln -s /usr/local/bin/wstun /usr/bin/wstun
#RUN pip3 install iotronic-lightningrod
RUN pip3 install --upgrade pip
COPY data/dist/iotronic_lightningrod-*.tar.gz /tmp/
RUN pip3 install /tmp/iotronic_lightningrod-*.tar.gz
RUN sed -i "s|listen 80 default_server;|listen 50000 default_server;|g" /etc/nginx/sites-available/default
RUN sed -i "s|80 default_server;|50000 default_server;|g" /etc/nginx/sites-available/default
@ -35,7 +55,7 @@ RUN /usr/local/bin/lr_install
VOLUME /var/lib/iotronic
RUN ln -s /usr/local/bin/wstun /usr/bin/wstun
#RUN ln -s /usr/local/bin/wstun /usr/bin/wstun
#CMD [ "/usr/sbin/nginx"]
#CMD [ "/usr/local/bin/lightning-rod"]