Browse Source

Release 0.5.0:

- added support to wstun allow list
- added force renew rpc/api
- added DeviceRestSubmit allowed action
- added checks in create and enable webservice
- added ServicesStatus RPC/API
- added hard delete device procedures
- added support for LR "freedom" edition
- added checks if dns or webservice already exists
- check in device creation for duplicated device name
- updated nginx confs

Change-Id: Idb95e63cc4d732f69331ebb585ee576ae872ac5c
changes/40/816540/1
Nicola Peditto 9 months ago
parent
commit
3e5782eb1f
  1. 46
      iotronic/api/controllers/v1/board.py
  2. 2
      iotronic/api/controllers/v1/location.py
  3. 1
      iotronic/api/controllers/v1/types.py
  4. 12
      iotronic/common/exception.py
  5. 663
      iotronic/conductor/endpoints.py
  6. 1
      iotronic/conductor/manager.py
  7. 22
      iotronic/conductor/rpcapi.py
  8. 23
      iotronic/db/sqlalchemy/alembic/versions/10460765f337_extended_board_code_attribute.py
  9. 64
      iotronic/db/sqlalchemy/alembic/versions/57a99337e843_hard_device_delete.py
  10. 9
      iotronic/db/sqlalchemy/api.py
  11. 16
      iotronic/db/sqlalchemy/models.py
  12. 18
      iotronic/objects/board.py
  13. 40
      iotronic/objects/enabledwebservice.py
  14. 7
      iotronic/objects/plugin.py
  15. 69
      iotronic/wamp/agent.py
  16. 13
      iotronic/wamp/proxies/nginx.py

46
iotronic/api/controllers/v1/board.py

@ -416,7 +416,8 @@ class BoardPluginsController(rest.RestController):
class BoardServicesController(rest.RestController):
_custom_actions = {
'action': ['POST'],
'restore': ['GET']
'restore': ['GET'],
'status': ['GET']
}
def __init__(self, board_ident):
@ -462,6 +463,7 @@ class BoardServicesController(rest.RestController):
except exception:
return exception
# add here the check on force parameter
rpc_board.check_if_online()
result = pecan.request.rpcapi.action_service(pecan.request.context,
@ -491,11 +493,33 @@ class BoardServicesController(rest.RestController):
return self._get_services_on_board_collection(rpc_board.uuid)
@expose.expose(wtypes.text, status_code=200)
def status(self):
"""Get status of all services in a board.
:param board_ident: UUID or logical name of a board.
"""
# context = pecan.request.context
# cdict = context.to_policy_values()
# policy.authorize('iot:board:delete', cdict, cdict)
rpc_board = api_utils.get_rpc_board(self.board_ident)
rpc_board.check_if_online()
result = pecan.request.rpcapi.status_services_on_board(
pecan.request.context,
rpc_board.uuid
)
return result
class BoardWebservicesController(rest.RestController):
_custom_actions = {
'enable': ['POST'],
'disable': ['DELETE']
'disable': ['DELETE'],
'renew': ['GET']
}
invalid_sort_key_list = ['extra', ]
@ -575,6 +599,7 @@ class BoardWebservicesController(rest.RestController):
:param Webservice: a Webservice within the request body.
"""
context = pecan.request.context
cdict = context.to_policy_values()
policy.authorize('iot:webservice:create', cdict, cdict)
@ -652,6 +677,23 @@ class BoardWebservicesController(rest.RestController):
pecan.request.rpcapi.disable_webservice(pecan.request.context,
rpc_board.uuid)
@expose.expose(None)
def renew(self):
"""Renew webservices certificates in a board.
:param board_ident: UUID or logical name of a board.
"""
# context = pecan.request.context
# cdict = context.to_policy_values()
# policy.authorize('iot:board:delete', cdict, cdict)
rpc_board = api_utils.get_rpc_board(self.board_ident)
rpc_board.check_if_online()
pecan.request.rpcapi.renew_webservice(pecan.request.context,
rpc_board.uuid)
class BoardPortsController(rest.RestController):

2
iotronic/api/controllers/v1/location.py

@ -31,7 +31,7 @@ class Location(base.APIBase):
fields = list(objects.Location.fields)
for k in fields:
# Skip fields we do not expose.
if k != 'created_at':
if k is not 'created_at':
if not hasattr(self, k):
continue
self.fields.append(k)

1
iotronic/api/controllers/v1/types.py

@ -327,7 +327,6 @@ class LocalLinkConnectionType(wtypes.UserType):
return None
return LocalLinkConnectionType.validate(value)
locallinkconnectiontype = LocalLinkConnectionType()

12
iotronic/common/exception.py

@ -260,6 +260,10 @@ class BoardAssociated(InvalidState):
message = _("Board %(board)s is associated with instance %(instance)s.")
class BoardNameAlreadyExists(Conflict):
message = _("A board with %(name)s is already associated to another board.")
class PortNotFound(NotFound):
message = _("Port %(port)s could not be found.")
@ -665,8 +669,12 @@ class WebserviceAlreadyExists(Conflict):
class EnabledWebserviceNotFound(NotFound):
message = _("No Webservice enabled for %(enabled_webservice)s.")
message = _("Webservice module not enabled for %(enabled_webservice)s device.")
class EnabledWebserviceAlreadyExists(Conflict):
message = _("Already enabled for %(enabled_webservice)s.")
message = _("Already enabled for %(enabled_webservice)s [req: %(req)s].")
class DnsWebserviceAlreadyExists(Conflict):
message = _("DNS %(dns)s already exists [req: %(req)s].")

663
iotronic/conductor/endpoints.py

@ -23,6 +23,7 @@ except Exception:
import random
import socket
import json
from oslo_config import cfg
@ -46,6 +47,7 @@ Port = list()
SERVICE_PORT_LIST = []
def versionCompare(v1, v2):
"""Method to compare two versions.
Return 1 if v2 is smaller,
@ -53,20 +55,25 @@ def versionCompare(v1, v2):
0 if equal
"""
v1_list = v1.split(".")[:3]
v2_list = v2.split(".")[:3]
i = 0
while (i < len(v1_list)):
if "freedom" in v1:
return 1
else:
# v2 > v1
if int(v2_list[i]) > int(v1_list[i]):
return -1
# v1 > v1
if int(v1_list[i]) > int(v2_list[i]):
return 1
i += 1
# v2 == v1
return 0
v1_list = v1.split(".")[:3]
v2_list = v2.split(".")[:3]
i = 0
while (i < len(v1_list)):
# v2 > v1
if int(v2_list[i]) > int(v1_list[i]):
return -1
# v1 > v1
if int(v1_list[i]) > int(v2_list[i]):
return 1
i += 1
# v2 == v1
return 0
def new_req(ctx, board, type, action, main_req=None, pending_requests=0):
@ -154,6 +161,7 @@ def create_record_dns_webservice(ctx, board, webs_name, board_dns, zone):
LOG.debug('using %s %s %s', webs_name + "." + board_dns,
ip, zone)
# CHECK IF DNS EXISTS
designate.create_record(webs_name + "." + board_dns, ip,
zone)
@ -170,8 +178,8 @@ def create_record_dns(ctx, board, board_dns, zone):
LOG.debug('using %s %s %s', board_dns,
ip, zone)
designate.create_record(board_dns, ip,
zone)
# CHECK IF DNS EXISTS
designate.create_record(board_dns, ip, zone)
LOG.debug('Configure Web Proxy on WampAgent %s (%s) for board %s',
board.agent, ip, board.uuid)
@ -253,20 +261,34 @@ class ConductorEndpoint(object):
board_id)
board = objects.Board.get_by_uuid(ctx, board_id)
result = None
if board.is_online():
prov = Provisioner()
prov.conf_clean()
p = prov.get_config()
LOG.debug('sending this conf %s', p)
try:
result = self.execute_on_board(ctx,
board_id,
'destroyBoard',
(p,))
'DeviceFactoryReset',
())
except exception:
return exception
exposed_list = objects.ExposedService.get_by_board_uuid(ctx,
board_id)
LOG.debug('starting the wamp client')
cctx = self.wamp_agent_client.prepare(server=board.agent)
LOG.debug('Service to remove from allowlist:')
LOG.debug(exposed_list)
for exposed in exposed_list:
LOG.debug(exposed.public_port)
cctx.call(ctx, 'remove_from_allowlist',
device=board_id,
port=exposed.public_port)
board.destroy()
if result:
result = manage_result(result, 'destroyBoard', board_id)
LOG.debug(result)
@ -280,19 +302,26 @@ class ConductorEndpoint(object):
return serializer.serialize_entity(ctx, board)
def create_board(self, ctx, board_obj, location_obj):
new_board = serializer.deserialize_entity(ctx, board_obj)
LOG.debug('Creating board %s',
new_board.name)
new_location = serializer.deserialize_entity(ctx, location_obj)
new_board.create()
new_location.board_id = new_board.id
new_location.create()
LOG.debug('Creating board %s', new_board.name)
board_exists = objects.Board.exists_by_name(ctx, new_board.name)
#print(board_exists)
if board_exists:
raise exception.BoardNameAlreadyExists(name=new_board.name)
else:
new_location = serializer.deserialize_entity(ctx, location_obj)
new_board.create()
new_location.board_id = new_board.id
new_location.create()
return serializer.serialize_entity(ctx, new_board)
return serializer.serialize_entity(ctx, new_board)
def execute_on_board(self, ctx, board_uuid, wamp_rpc_call, wamp_rpc_args,
main_req=None):
LOG.debug('Executing \"%s\" on the board: %s (req %s)',
LOG.debug('Executing \"%s\" on the board: %s (main_req %s)',
wamp_rpc_call, board_uuid, main_req)
board = objects.Board.get_by_uuid(ctx, board_uuid)
@ -308,18 +337,20 @@ class ConductorEndpoint(object):
req = new_req(ctx, board, objects.request.BOARD, wamp_rpc_call,
main_req)
LOG.info(" - exec_request: " + str(req.uuid))
res = new_res(ctx, board, req.uuid)
cctx = self.wamp_agent_client.prepare(server=board.agent)
# for previous LR version (to be removed asap)
if (versionCompare(board.lr_version, "0.4.9") == -1):
response = cctx.call(ctx, 's4t_invoke_wamp',
wamp_rpc_call=full_wamp_call,
data=wamp_rpc_args)
else:
response = cctx.call(ctx, 's4t_invoke_wamp',
wamp_rpc_call=full_wamp_call,
req=req,
@ -351,7 +382,7 @@ class ConductorEndpoint(object):
board = objects.Board.get(ctx, board_uuid)
if not board.is_online():
raise exception.BoardNotConnected(board=board.uuid)
objects.board.is_valid_action(action)
try:
@ -484,6 +515,10 @@ class ConductorEndpoint(object):
service = objects.Service.get(ctx, service_uuid)
objects.service.is_valid_action(action)
board = objects.Board.get_by_uuid(ctx, board_uuid)
if not board.is_online():
raise exception.BoardNotConnected(board=board.uuid)
if action == "ServiceEnable":
LOG.info('Enabling service with id %s into the board %s',
service_uuid, board_uuid)
@ -493,11 +528,23 @@ class ConductorEndpoint(object):
service_uuid)
return exception.ServiceAlreadyExposed(uuid=service_uuid)
except Exception:
public_port = random_public_port()
if not public_port:
return exception.NotEnoughPortForService()
# Add into service allow list
#if cfg.CONF.conductor.service_allow_list:
# addin_allowlist(board_uuid, public_port)
LOG.debug('starting the wamp client')
cctx = self.wamp_agent_client.prepare(server=board.agent)
cctx.call(ctx, 'addin_allowlist',
device=board_uuid,
port=public_port)
res = self.execute_on_board(ctx, board_uuid, action,
(service, public_port))
result = manage_result(res, action, board_uuid)
@ -531,6 +578,18 @@ class ConductorEndpoint(object):
except exception:
pass
exposed.destroy()
# Remove from service allow list
#if cfg.CONF.conductor.service_allow_list:
#remove_from_allowlist(board_uuid, exposed.public_port)
LOG.debug('starting the wamp client')
cctx = self.wamp_agent_client.prepare(server=board.agent)
cctx.call(ctx, 'remove_from_allowlist',
device=board_uuid,
port=exposed.public_port)
return result
elif action == "ServiceRestore":
@ -559,6 +618,25 @@ class ConductorEndpoint(object):
return 0
def status_services_on_board(self, ctx, board_uuid):
LOG.info('Getting services status into devide %s',
board_uuid)
action = "ServicesStatus"
board = objects.Board.get(ctx, board_uuid)
if not board.is_online():
raise exception.BoardNotConnected(board=board.uuid)
try:
result = self.execute_on_board(ctx, board_uuid, action, ())
except exception:
return exception
result = manage_result(result, action, board_uuid)
LOG.debug(result)
return result
def create_port_on_board(self, ctx, board_uuid, network_uuid,
subnet_uuid, security_groups=None):
@ -691,57 +769,102 @@ class ConductorEndpoint(object):
def create_webservice(self, ctx, webservice_obj):
newwbs = serializer.deserialize_entity(ctx, webservice_obj)
LOG.debug('Creating webservice %s',
newwbs.name)
LOG.debug('Creating webservice %s', newwbs.name)
board = objects.Board.get_by_uuid(ctx, newwbs.board_uuid)
if not board.is_online():
raise exception.BoardNotConnected(board=board.uuid)
en_webservice = objects.enabledwebservice. \
EnabledWebservice.get_by_board_uuid(ctx,
newwbs.board_uuid)
LOG.info(" - expose "+str(newwbs.name)+" in " + str(board.name) + " ["+str(newwbs.board_uuid)+"]")
list_webs = objects.Webservice.list(
ctx,
filters={'board_uuid': newwbs.board_uuid}
)
dns_check=True
webs_found=None
full_zone_domain = en_webservice.dns + "." + en_webservice.zone
dns_domain = newwbs.name + "." + full_zone_domain
for webs in list_webs:
wboard = objects.Board.get(ctx, webs.board_uuid)
LOG.info(" --> " + str(webs.name) + ", " + str(wboard.name) + ", " + str(webs.board_uuid) + ", " + str(webs.port) )
if (str(newwbs.board_uuid) == str(webs.board_uuid)) and (newwbs.name == str(webs.name)):
dns_check = False
webs_found = webs
break
create_record_dns_webservice(ctx, board, newwbs.name,
en_webservice.dns,
en_webservice.zone)
if dns_check:
LOG.debug('Creating webservice with full domain %s',
dns_domain)
LOG.info("Webservice can be exposed!")
list_webs = objects.Webservice.list(ctx,
filters={
'board_uuid': newwbs.board_uuid
})
list_dns = full_zone_domain + ","
for webs in list_webs:
dname = webs.name + "." + full_zone_domain + ","
list_dns = list_dns + dname
en_webservice = objects.enabledwebservice. \
EnabledWebservice.get_by_board_uuid(ctx,
newwbs.board_uuid)
list_dns = list_dns + dns_domain
full_zone_domain = en_webservice.dns + "." + en_webservice.zone
dns_domain = newwbs.name + "." + full_zone_domain
try:
self.execute_on_board(ctx,
newwbs.board_uuid,
'ExposeWebservice',
(full_zone_domain,
dns_domain,
newwbs.port,
list_dns,))
except exception:
return exception
create_record_dns_webservice(ctx, board, newwbs.name,
en_webservice.dns,
en_webservice.zone)
cctx = self.wamp_agent_client.prepare(server=board.agent)
cctx.call(ctx, 'add_redirect', board_dns=en_webservice.dns,
zone=en_webservice.zone, dns=newwbs.name)
cctx.call(ctx, 'reload_proxy')
LOG.debug('Creating webservice with full domain %s',
dns_domain)
list_dns = full_zone_domain + ","
for webs in list_webs:
dname = webs.name + "." + full_zone_domain + ","
list_dns = list_dns + dname
list_dns = list_dns + dns_domain
try:
res = self.execute_on_board(ctx,
newwbs.board_uuid,
'ExposeWebservice',
(full_zone_domain,
dns_domain,
newwbs.port,
list_dns,))
except exception:
return exception
cctx = self.wamp_agent_client.prepare(server=board.agent)
cctx.call(ctx, 'add_redirect', board_dns=en_webservice.dns,
zone=en_webservice.zone, dns=newwbs.name)
cctx.call(ctx, 'reload_proxy')
newwbs.create()
newwbs.create()
result = manage_result(res, "ExposeWebservice", board.uuid)
LOG.info(result)
#return result
return serializer.serialize_entity(ctx, newwbs)
return serializer.serialize_entity(ctx, newwbs)
else:
mreq = new_req(ctx, board, objects.request.BOARD,
"expose_webservice", pending_requests=0)
LOG.info(" - request: " + str(mreq.uuid))
msg="Webservice already exposed for this device!"
w_msg = wm.WampWarning(msg, req_id=mreq.uuid)
res = new_res(ctx, board, mreq.uuid)
res.result = w_msg.result
res.message = w_msg.message
res.save()
mreq.status = objects.request.COMPLETED
mreq.save()
result = manage_result(w_msg, "ExposeWebservice", board.uuid)
LOG.info(result)
#return result
return serializer.serialize_entity(ctx, webs_found)
def destroy_webservice(self, ctx, webservice_id):
LOG.info('Destroying webservice with id %s',
@ -750,49 +873,53 @@ class ConductorEndpoint(object):
wbsrv = objects.Webservice.get_by_uuid(ctx, webservice_id)
board = objects.Board.get_by_uuid(ctx, wbsrv.board_uuid)
if not board.is_online():
raise exception.BoardNotConnected(board=board.uuid)
#if not board.is_online():
# raise exception.BoardNotConnected(board=board.uuid)
en_webservice = objects.enabledwebservice. \
EnabledWebservice.get_by_board_uuid(ctx,
wbsrv.board_uuid)
full_zone_domain = en_webservice.dns + "." + en_webservice.zone
dns_domain = wbsrv.name + "." + full_zone_domain
if board.is_online():
full_zone_domain = en_webservice.dns + "." + en_webservice.zone
dns_domain = wbsrv.name + "." + full_zone_domain
list_webs = objects.Webservice.list(ctx,
filters={
'board_uuid': wbsrv.board_uuid
})
list_webs = objects.Webservice.list(ctx,
filters={
'board_uuid': wbsrv.board_uuid
})
list_dns = full_zone_domain + ","
for webs in list_webs:
if webs.name != wbsrv.name:
dname = webs.name + "." + full_zone_domain + ","
list_dns = list_dns + dname
list_dns = list_dns[:-1]
list_dns = full_zone_domain + ","
for webs in list_webs:
if webs.name != wbsrv.name:
dname = webs.name + "." + full_zone_domain + ","
list_dns = list_dns + dname
list_dns = list_dns[:-1]
try:
# result =
self.execute_on_board(ctx,
wbsrv.board_uuid,
'UnexposeWebservice',
(dns_domain, list_dns,))
except exception:
return exception
try:
# result =
self.execute_on_board(ctx,
wbsrv.board_uuid,
'UnexposeWebservice',
(dns_domain, list_dns,))
except exception:
return exception
if board.agent == None:
raise exception.BoardInvalidStatus(uuid=board.uuid,
status=board.status)
if board.agent == None:
raise exception.BoardInvalidStatus(uuid=board.uuid,
status=board.status)
cctx = self.wamp_agent_client.prepare(server=board.agent)
cctx.call(ctx, 'remove_redirect', board_dns=en_webservice.dns,
zone=en_webservice.zone, dns=wbsrv.name)
cctx.call(ctx, 'reload_proxy')
cctx = self.wamp_agent_client.prepare(server=board.agent)
cctx.call(ctx, 'remove_redirect', board_dns=en_webservice.dns,
zone=en_webservice.zone, dns=wbsrv.name)
cctx.call(ctx, 'reload_proxy')
wbsrv.destroy()
designate.delete_record(wbsrv.name + "." + en_webservice.dns,
en_webservice.zone)
return
def enable_webservice(self, ctx, dns, zone, email, board_uuid):
@ -804,110 +931,195 @@ class ConductorEndpoint(object):
if board.agent == None:
raise exception.BoardInvalidStatus(uuid=board.uuid,
status=board.status)
#print("DNS: " + str(dns))
mreq = new_req(ctx, board, objects.request.BOARD,
"enable_webservice", pending_requests=3)
avl_webservice = objects.enabledwebservice.EnabledWebservice.checkDnsAvailable(ctx, dns)
#print("Available: " + str(avl_webservice))
try:
create_record_dns(ctx, board, dns, zone)
except exception:
return exception
LOG.info("Enabling Webservice module for device " + str(board.name) + " ["+board_uuid+"]")
try:
en_webservice = objects.enabledwebservice. \
EnabledWebservice.get_by_board_uuid(ctx,
board.uuid)
if avl_webservice:
LOG.debug('Webservice data already exists for board %s',
board.uuid)
https_port = en_webservice.https_port
http_port = en_webservice.http_port
en_webservice = objects.enabledwebservice.EnabledWebservice.isWebserviceEnabled(ctx, board.uuid)
except Exception:
if(en_webservice):
https_port = random_public_port()
http_port = random_public_port()
if not https_port or not http_port:
return exception.NotEnoughPortForService()
en_webservice = {
'board_uuid': board.uuid,
'http_port': http_port,
'https_port': https_port,
'dns': dns,
'zone': zone
}
en_webservice = objects.enabledwebservice.EnabledWebservice(
ctx, **en_webservice)
mreq = new_req(ctx, board, objects.request.BOARD,
"enable_webservice", pending_requests=0)
LOG.info(" - request: " + str(mreq.uuid))
LOG.debug('Save webservice data %s for board %s', dns + "." + zone,
board.uuid)
en_webservice.create()
res = new_res(ctx, board, mreq.uuid)
LOG.debug('Open ports on WampAgent %s for http and %s for https '
'on board %s', http_port, https_port, board.uuid)
en_webservice = objects.enabledwebservice.EnabledWebservice.get_by_board_uuid(ctx, board.uuid)
service = objects.Service.get_by_name(ctx, 'webservice')
LOG.info("Webservice and dns already enabled for this device:")
full_zone_domain = en_webservice.dns + "." + en_webservice.zone
res = self.execute_on_board(ctx, board.uuid, "ServiceEnable",
(service, http_port,), main_req=mreq.uuid)
result = manage_result(res, "ServiceEnable", board.uuid)
LOG.info(" --> " + str(full_zone_domain))
exp_data = {
'board_uuid': board_uuid,
'service_uuid': service.uuid,
'public_port': http_port,
}
exposed = objects.ExposedService(ctx, **exp_data)
exposed.create()
msg="Webservice and dns already enabled for this device!"
w_msg = wm.WampWarning(msg, req_id=mreq.uuid)
LOG.debug(result)
res.result = w_msg.result
res.message = w_msg.message
res.save()
service = objects.Service.get_by_name(ctx, 'webservice_ssl')
mreq.status = objects.request.COMPLETED
mreq.save()
res = self.execute_on_board(ctx, board.uuid, "ServiceEnable",
(service, https_port,), main_req=mreq.uuid)
result = manage_result(res, "ServiceEnable", board.uuid)
result = manage_result(w_msg, "EnableWebservice", board.uuid)
exp_data = {
'board_uuid': board_uuid,
'service_uuid': service.uuid,
'public_port': https_port,
}
exposed = objects.ExposedService(ctx, **exp_data)
exposed.create()
LOG.info(result)
LOG.debug(result)
raise exception.EnabledWebserviceAlreadyExists(enabled_webservice=full_zone_domain, req=mreq.uuid)
cctx = self.wamp_agent_client.prepare(server=board.agent)
cctx.call(ctx, 'enable_webservice', board=dns,
https_port=https_port, http_port=http_port, zone=zone)
cctx.call(ctx, 'reload_proxy')
#return serializer.serialize_entity(ctx, en_webservice)
LOG.debug('Configure Web Proxy on Board %s with dns %s (email: %s) ',
board.uuid, dns, email)
try:
self.execute_on_board(ctx,
board.uuid,
'EnableWebService',
(dns + "." + zone, email,),
main_req=mreq.uuid)
except exception:
return exception
else:
mreq = new_req(ctx, board, objects.request.BOARD,
"enable_webservice", pending_requests=3)
cctx.call(ctx, 'add_redirect', board_dns=dns, zone=zone)
cctx.call(ctx, 'reload_proxy')
try:
create_record_dns(ctx, board, dns, zone)
except exception:
return exception
return serializer.serialize_entity(ctx, en_webservice)
try:
en_webservice = objects.enabledwebservice. \
EnabledWebservice.get_by_board_uuid(ctx,
board.uuid)
LOG.debug('Webservice data already exists for board %s',
board.uuid)
https_port = en_webservice.https_port
http_port = en_webservice.http_port
except Exception:
https_port = random_public_port()
http_port = random_public_port()
if not https_port or not http_port:
return exception.NotEnoughPortForService()
en_webservice = {
'board_uuid': board.uuid,
'http_port': http_port,
'https_port': https_port,
'dns': dns,
'zone': zone
}
en_webservice = objects.enabledwebservice.EnabledWebservice(
ctx, **en_webservice)
LOG.debug('Save webservice data %s for board %s', dns + "." + zone,
board.uuid)
en_webservice.create()
LOG.debug('Open ports on WampAgent %s for http and %s for https '
'on board %s', http_port, https_port, board.uuid)
service = objects.Service.get_by_name(ctx, 'webservice')
LOG.debug('starting the wamp client')
cctx = self.wamp_agent_client.prepare(server=board.agent)
cctx.call(ctx, 'addin_allowlist',
device=board.uuid,
port=http_port)
cctx.call(ctx, 'addin_allowlist',
device=board.uuid,
port=https_port)
res = self.execute_on_board(ctx, board.uuid, "ServiceEnable",
(service, http_port,), main_req=mreq.uuid)
result = manage_result(res, "ServiceEnable", board.uuid)
exp_data = {
'board_uuid': board_uuid,
'service_uuid': service.uuid,
'public_port': http_port,
}
exposed = objects.ExposedService(ctx, **exp_data)
exposed.create()
LOG.debug(result)
service = objects.Service.get_by_name(ctx, 'webservice_ssl')
res = self.execute_on_board(ctx, board.uuid, "ServiceEnable",
(service, https_port,), main_req=mreq.uuid)
result = manage_result(res, "ServiceEnable", board.uuid)
exp_data = {
'board_uuid': board_uuid,
'service_uuid': service.uuid,
'public_port': https_port,
}
exposed = objects.ExposedService(ctx, **exp_data)
exposed.create()
LOG.debug(result)
cctx = self.wamp_agent_client.prepare(server=board.agent)
cctx.call(ctx, 'enable_webservice', board=dns,
https_port=https_port, http_port=http_port, zone=zone)
cctx.call(ctx, 'reload_proxy')
LOG.debug('Configure Web Proxy on Board %s with dns %s (email: %s) ',
board.uuid, dns, email)
try:
self.execute_on_board(ctx,
board.uuid,
'EnableWebService',
(dns + "." + zone, email,),
main_req=mreq.uuid)
except exception:
return exception
cctx.call(ctx, 'add_redirect', board_dns=dns, zone=zone)
cctx.call(ctx, 'reload_proxy')
return serializer.serialize_entity(ctx, en_webservice)
else:
mreq = new_req(ctx, board, objects.request.BOARD,
"enable_webservice", pending_requests=0)
LOG.info(" - request: " + str(mreq.uuid))
res = new_res(ctx, board, mreq.uuid)
msg="DNS already exists!"
w_msg = wm.WampWarning(msg, req_id=mreq.uuid)
res.result = w_msg.result
res.message = w_msg.message
res.save()
mreq.status = objects.request.COMPLETED
mreq.save()
result = manage_result(w_msg, "EnableWebservice", board.uuid)
LOG.info(" - result: " + str(result))
raise exception.DnsWebserviceAlreadyExists(dns=dns, req=mreq.uuid)
def disable_webservice(self, ctx, board_uuid):
LOG.info('Disabling webservice on board id %s',
board_uuid)
board = objects.Board.get_by_uuid(ctx, board_uuid)
if not board.is_online():
raise exception.BoardNotConnected(board=board.uuid)
#if not board.is_online():
# raise exception.BoardNotConnected(board=board.uuid)
if board.agent == None:
raise exception.BoardInvalidStatus(uuid=board.uuid,
@ -923,7 +1135,7 @@ class ConductorEndpoint(object):
https_port = en_webservice.https_port
http_port = en_webservice.http_port
LOG.debug('Disable Webservices ports %s for http and %s for https '
LOG.debug('Disable Webservices ports %s for http and %s for https '
'on board %s', http_port, https_port, board.uuid)
service = objects.Service.get_by_name(ctx, 'webservice')
@ -932,9 +1144,11 @@ class ConductorEndpoint(object):
board_uuid,
service.uuid)
res = self.execute_on_board(ctx, board.uuid, "ServiceDisable",
if board.is_online():
res = self.execute_on_board(ctx, board.uuid, "ServiceDisable",
(service,), main_req=mreq.uuid)
LOG.debug(res.message)
LOG.debug(res.message)
exposed.destroy()
service = objects.Service.get_by_name(ctx, 'webservice_ssl')
@ -942,9 +1156,13 @@ class ConductorEndpoint(object):
exposed = objects.ExposedService.get(ctx,
board_uuid,
service.uuid)
res = self.execute_on_board(ctx, board.uuid, "ServiceDisable",
if board.is_online():
res = self.execute_on_board(ctx, board.uuid, "ServiceDisable",
(service,), main_req=mreq.uuid)
LOG.debug(res.message)
LOG.debug(res.message)
global SERVICE_PORT_LIST
try:
SERVICE_PORT_LIST.append(exposed.public_port)
@ -952,14 +1170,15 @@ class ConductorEndpoint(object):
pass
exposed.destroy()
try:
self.execute_on_board(ctx,
board.uuid,
'DisableWebService',
(),
main_req=mreq.uuid)
except exception:
return exception
if board.is_online():
try:
self.execute_on_board(ctx,
board.uuid,
'DisableWebService',
(),
main_req=mreq.uuid)
except exception:
return exception
webservice = objects.EnabledWebservice.get_by_board_uuid(
ctx, board_uuid)
@ -969,13 +1188,73 @@ class ConductorEndpoint(object):
designate.delete_record(webservice.dns, en_webservice.zone)
if board.is_online():
cctx = self.wamp_agent_client.prepare(server=board.agent)
cctx.call(ctx, 'disable_webservice', board=webservice.dns)
# cctx.call(ctx, 'remove_redirect', board_dns=en_webservice.dns,
# zone=en_webservice.zone)
cctx.call(ctx, 'reload_proxy')
LOG.debug('starting the wamp client')
cctx = self.wamp_agent_client.prepare(server=board.agent)
cctx.call(ctx, 'disable_webservice', board=webservice.dns)
cctx.call(ctx, 'remove_from_allowlist',
device=board.uuid,
port=http_port)
# cctx.call(ctx, 'remove_redirect', board_dns=en_webservice.dns,
# zone=en_webservice.zone)
cctx.call(ctx, 'reload_proxy')
cctx.call(ctx, 'remove_from_allowlist',
device=board.uuid,
port=https_port)
webservice.destroy()
return
def renew_webservice(self, ctx, board_uuid):
board = objects.Board.get_by_uuid(ctx, board_uuid)
if not board.is_online():
raise exception.BoardNotConnected(board=board.uuid)
if board.agent == None:
raise exception.BoardInvalidStatus(uuid=board.uuid,
status=board.status)
LOG.info('Renewing certificates on device %s', board.uuid)
en_webservice = objects.enabledwebservice.EnabledWebservice.isWebserviceEnabled(ctx, board.uuid)
if(en_webservice):
mreq = new_req(ctx, board, objects.request.BOARD,
"RenewWebservice", pending_requests=1)
res = self.execute_on_board(ctx, board.uuid, "RenewWebservice", (), main_req=mreq.uuid )
else:
mreq = new_req(ctx, board, objects.request.BOARD,
"RenewWebservice", pending_requests=0)
LOG.info(" - request: " + str(mreq.uuid))
msg="Webservice module not enabled for this device!"
w_msg = wm.WampWarning(msg, req_id=mreq.uuid)
res = new_res(ctx, board, mreq.uuid)
res.result = w_msg.result
res.message = w_msg.message
res.save()
mreq.status = objects.request.COMPLETED
mreq.save()
raise exception.EnabledWebserviceNotFound(enabled_webservice=board.uuid)
result = manage_result(res, "RenewWebservice", board.uuid)
LOG.info(" - " + str(result))
return serializer.serialize_entity(ctx, en_webservice)

1
iotronic/conductor/manager.py

@ -46,6 +46,7 @@ conductor_opts = [
cfg.IntOpt('service_port_max',
default=60000,
help='Max value for genereting random ports for services'),
]
CONF = cfg.CONF

22
iotronic/conductor/rpcapi.py

@ -291,6 +291,20 @@ class ConductorAPI(object):
return cctxt.call(context, 'restore_services_on_board',
board_uuid=board_uuid)
def status_services_on_board(self, context,
board_uuid, topic=None):
"""Get status of all services' tunnel on a board.
:param context: request context.
:param board_uuid: board id or uuid.
"""
cctxt = self.client.prepare(topic=topic or self.topic, version='1.0')
return cctxt.call(context, 'status_services_on_board',
board_uuid=board_uuid)
def create_port_on_board(self, context, board_uuid, network,
subnet, sec_groups, topic=None):
"""Add a port on a Board
@ -409,3 +423,11 @@ class ConductorAPI(object):
cctxt = self.client.prepare(topic=topic or self.topic, version='1.0')
return cctxt.call(context, 'disable_webservice',
board_uuid=board_uuid)
def renew_webservice(self, context, board_uuid, topic=None):
"""Renew webservice certificate.
"""
cctxt = self.client.prepare(topic=topic or self.topic, version='1.0')
return cctxt.call(context, 'renew_webservice',
board_uuid=board_uuid)

23
iotronic/db/sqlalchemy/alembic/versions/10460765f337_extended_board_code_attribute.py

@ -0,0 +1,23 @@
"""extended board code attribute
Revision ID: 10460765f337
Revises: 57a99337e843
Create Date: 2021-11-03 16:17:19.310762
"""
# revision identifiers, used by Alembic.
revision = '10460765f337'
down_revision = '57a99337e843'
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('boards', 'code',
existing_type=mysql.VARCHAR(length=36),
nullable=False)
# ### end Alembic commands ###

64
iotronic/db/sqlalchemy/alembic/versions/57a99337e843_hard_device_delete.py

@ -0,0 +1,64 @@
"""hard device delete
Revision ID: 57a99337e843
Revises: 76c628d60004
Create Date: 2020-07-07 15:45:20.892424
"""
# revision identifiers, used by Alembic.
revision = '57a99337e843'
down_revision = '76c628d60004'
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint('enabled_webservices_ibfk_1', 'enabled_webservices', type_='foreignkey')
op.create_foreign_key(None, 'enabled_webservices', 'boards', ['board_uuid'], ['uuid'], ondelete='CASCADE')
op.drop_constraint('exposed_services_ibfk_1', 'exposed_services', type_='foreignkey')
op.create_foreign_key(None, 'exposed_services', 'boards', ['board_uuid'], ['uuid'], ondelete='CASCADE')
op.drop_constraint('injection_plugins_ibfk_1', 'injection_plugins', type_='foreignkey')
op.create_foreign_key(None, 'injection_plugins', 'boards', ['board_uuid'], ['uuid'], ondelete='CASCADE')
op.drop_constraint('locations_ibfk_1', 'locations', type_='foreignkey')
op.create_foreign_key(None, 'locations', 'boards', ['board_id'], ['id'], ondelete='CASCADE')
op.drop_constraint('ports_on_boards_ibfk_1', 'ports_on_boards', type_='foreignkey')
op.create_foreign_key(None, 'ports_on_boards', 'boards', ['board_uuid'], ['uuid'], ondelete='CASCADE')
op.alter_column('requests', 'action',
existing_type=mysql.VARCHAR(length=20),
nullable=True)
op.alter_column('requests', 'destination_uuid',
existing_type=mysql.VARCHAR(length=36),
nullable=True)
op.alter_column('requests', 'pending_requests',
existing_type=mysql.INTEGER(display_width=11),
nullable=True)
op.alter_column('requests', 'status',
existing_type=mysql.VARCHAR(length=10),
nullable=True)
op.alter_column('requests', 'type',
existing_type=mysql.INTEGER(display_width=11),
nullable=True)
op.alter_column('requests', 'uuid',
existing_type=mysql.VARCHAR(length=36),
nullable=True)
op.alter_column('results', 'board_uuid',
existing_type=mysql.VARCHAR(length=36),
nullable=True)
op.alter_column('results', 'request_uuid',
existing_type=mysql.VARCHAR(length=36),
nullable=True)
op.alter_column('results', 'result',
existing_type=mysql.VARCHAR(length=10),
nullable=True)
op.drop_constraint('results_ibfk_1', 'results', type_='foreignkey')
op.drop_constraint('results_ibfk_2', 'results', type_='foreignkey')
op.drop_constraint('sessions_ibfk_1', 'sessions', type_='foreignkey')
op.create_foreign_key(None, 'sessions', 'boards', ['board_id'], ['id'], ondelete='CASCADE')
op.create_unique_constraint('uniq_webservices0uuid', 'webservices', ['uuid'])
op.drop_index('uniq_enabled_webservices0uuid', table_name='webservices')
op.drop_constraint('webservices_ibfk_1', 'webservices', type_='foreignkey')
op.create_foreign_key(None, 'webservices', 'boards', ['board_uuid'], ['uuid'], ondelete='CASCADE')
# ### end Alembic commands ###

9
iotronic/db/sqlalchemy/api.py

@ -1175,6 +1175,15 @@ class Connection(api.Connection):
# ENABLED_WEBSERIVCE api
def get_enabled_webservice_by_name(self, enabled_webservice_name):
query = model_query(models.EnabledWebservice).filter_by(
dns=enabled_webservice_name)
try:
return query.one()
except NoResultFound:
return False
#exception.EnabledWebserviceNotFound(enabled_webservice=enabled_webservice_name)
def get_enabled_webservice_by_id(self, enabled_webservice_id):
query = model_query(models.EnabledWebservice).filter_by(
id=enabled_webservice_id)

16
iotronic/db/sqlalchemy/models.py

@ -144,7 +144,7 @@ class Board(Base):
table_args())
id = Column(Integer, primary_key=True)
uuid = Column(String(36))
code = Column(String(25))
code = Column(String(36))
status = Column(String(15), nullable=True)
name = Column(String(255), nullable=True)
type = Column(String(255))
@ -169,7 +169,7 @@ class Location(Base):
longitude = Column(String(18), nullable=True)
latitude = Column(String(18), nullable=True)
altitude = Column(String(18), nullable=True)
board_id = Column(Integer, ForeignKey('boards.id'))
board_id = Column(Integer, ForeignKey('boards.id', ondelete="CASCADE"))
class SessionWP(Base):
@ -185,7 +185,7 @@ class SessionWP(Base):
valid = Column(Boolean, default=True)
session_id = Column(String(20))
board_uuid = Column(String(36))
board_id = Column(Integer, ForeignKey('boards.id'))
board_id = Column(Integer, ForeignKey('boards.id', ondelete="CASCADE"))
class Plugin(Base):
@ -213,7 +213,7 @@ class InjectionPlugin(Base):
__table_args__ = (
table_args())
id = Column(Integer, primary_key=True)
board_uuid = Column(String(36), ForeignKey('boards.uuid'))
board_uuid = Column(String(36), ForeignKey('boards.uuid', ondelete="CASCADE"))
plugin_uuid = Column(String(36), ForeignKey('plugins.uuid'))
onboot = Column(Boolean, default=False)
status = Column(String(15))
@ -242,7 +242,7 @@ class ExposedService(Base):
__table_args__ = (
table_args())
id = Column(Integer, primary_key=True)
board_uuid = Column(String(36), ForeignKey('boards.uuid'))
board_uuid = Column(String(36), ForeignKey('boards.uuid', ondelete="CASCADE"))
service_uuid = Column(String(36), ForeignKey('services.uuid'))
public_port = Column(Integer)
@ -256,7 +256,7 @@ class Port(Base):
# table_args()
# )
id = Column(Integer, primary_key=True)
board_uuid = Column(String(40), ForeignKey('boards.uuid'))
board_uuid = Column(String(40), ForeignKey('boards.uuid', ondelete="CASCADE"))
uuid = Column(String(40))
VIF_name = Column(String(30))
# project = Column(String(36))
@ -298,7 +298,7 @@ class Webservice(Base):
uuid = Column(String(36))
port = Column(Integer)
name = Column(String(45))
board_uuid = Column(String(36), ForeignKey('boards.uuid'), nullable=True)
board_uuid = Column(String(36), ForeignKey('boards.uuid', ondelete="CASCADE"), nullable=True)
secure = Column(Boolean, default=True)
extra = Column(JSONEncodedDict)
@ -308,7 +308,7 @@ class EnabledWebservice(Base):
__tablename__ = 'enabled_webservices'
id = Column(Integer, primary_key=True)
board_uuid = Column(String(36), ForeignKey('boards.uuid'), nullable=True)
board_uuid = Column(String(36), ForeignKey('boards.uuid', ondelete="CASCADE"), nullable=True)
http_port = Column(Integer)
https_port = Column(Integer)
dns = Column(String(100))

18
iotronic/objects/board.py