Browse Source

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
changes/42/816442/1
Nicola Peditto 9 months ago
parent
commit
47d682fff5
  1. 2
      iotronic_lightningrod/common/pam.py
  2. 56
      iotronic_lightningrod/devices/arancino.py
  3. 37
      iotronic_lightningrod/devices/gpio/arancino.py
  4. 5
      iotronic_lightningrod/devices/yun.py
  5. 115
      iotronic_lightningrod/lightningrod.py
  6. 171
      iotronic_lightningrod/modules/device_manager.py
  7. 93
      iotronic_lightningrod/modules/plugin_manager.py
  8. 46
      iotronic_lightningrod/modules/proxies/nginx.py
  9. 158
      iotronic_lightningrod/modules/rest_manager.py
  10. 89
      iotronic_lightningrod/modules/service_manager.py
  11. 25
      iotronic_lightningrod/modules/utils.py
  12. BIN
      iotronic_lightningrod/modules/web/static/images/stack4thingsblack.png
  13. BIN
      iotronic_lightningrod/modules/web/static/images/stack4thingslogo.png
  14. 8
      iotronic_lightningrod/modules/web/templates/home.html
  15. 5
      iotronic_lightningrod/modules/web/templates/login.html
  16. 85
      iotronic_lightningrod/modules/web/templates/status.html
  17. 79
      iotronic_lightningrod/modules/webservice_manager.py
  18. 21
      scripts/device_bkp_rest
  19. 61
      scripts/lr_configure
  20. 8
      scripts/lr_info
  21. 2
      scripts/lr_install
  22. 1
      tox.ini
  23. 28
      utils/docker/x86_64/Dockerfile

2
iotronic_lightningrod/common/pam.py

@ -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 = [

56
iotronic_lightningrod/devices/arancino.py

@ -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

37
iotronic_lightningrod/devices/gpio/arancino.py

@ -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)

5
iotronic_lightningrod/devices/yun.py

@ -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

115
iotronic_lightningrod/lightningrod.py

@ -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)
# 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...")
LOG.info("Lightning-rod modules loaded.")
LOG.info("\n\nListening...")
def moduleReloadInfo(session):

171
iotronic_lightningrod/modules/device_manager.py

@ -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():

93
iotronic_lightningrod/modules/plugin_manager.py

@ -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"

46
iotronic_lightningrod/modules/proxies/nginx.py

@ -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;

158
iotronic_lightningrod/modules/rest_manager.py

@ -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,55 +176,84 @@ 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):
wstun_status = service_manager.wstun_status()
if wstun_status == 0:
wstun_status = "Online"
else:
wstun_status = "Offline"
f_session['status'] = str(board.status)
service_list = service_manager.services_list("html")
if service_list == "":
service_list = "no services exposed!"
wstun_status = service_manager.wstun_status()
if wstun_status == 0:
wstun_status = "Online"
else:
wstun_status = "Offline"
lr_cty = "N/A"
from iotronic_lightningrod.lightningrod import wport
sock_bundle = lr_utils.get_socket_info(wport)
service_list = service_manager.services_list("html")
if service_list == "":
service_list = "no services exposed!"
if sock_bundle != "N/A":
lr_cty = sock_bundle[2] + " - " + sock_bundle[0] \
+ " - " + sock_bundle[1]
webservice_list = ""
nginx_path = "/etc/nginx/conf.d/"
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")
)
}
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))
]
return render_template('status.html', **info)
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")
)
}
else:
return redirect(url_for('login', next=request.endpoint))
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():
@ -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)

89
iotronic_lightningrod/modules/service_manager.py

@ -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": []
}
""" 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=thr_list, req_id=req_id)
w_msg = WM.WampSuccess(msg=tuns, req_id=req_id)
return w_msg.serialize()

25
iotronic_lightningrod/modules/utils.py

@ -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!

BIN
iotronic_lightningrod/modules/web/static/images/stack4thingsblack.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

BIN
iotronic_lightningrod/modules/web/static/images/stack4thingslogo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

8
iotronic_lightningrod/modules/web/templates/home.html

@ -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 %}

5
iotronic_lightningrod/modules/web/templates/login.html

@ -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 }}">

85
iotronic_lightningrod/modules/web/templates/status.html

@ -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>