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
This commit is contained in:
Nicola Peditto 2021-11-03 18:11:36 +01:00
parent 6e7ddfaf21
commit 3e5782eb1f
16 changed files with 793 additions and 217 deletions

View File

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

View File

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

View File

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

View File

@ -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].")

View File

@ -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()
return serializer.serialize_entity(ctx, new_board)
new_board = serializer.deserialize_entity(ctx, board_obj)
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)
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
create_record_dns_webservice(ctx, board, newwbs.name,
en_webservice.dns,
en_webservice.zone)
LOG.debug('Creating webservice with full domain %s',
dns_domain)
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
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
list_dns = list_dns + dns_domain
if dns_check:
try:
self.execute_on_board(ctx,
newwbs.board_uuid,
'ExposeWebservice',
(full_zone_domain,
dns_domain,
newwbs.port,
list_dns,))
except exception:
return exception
LOG.info("Webservice can be exposed!")
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')
en_webservice = objects.enabledwebservice. \
EnabledWebservice.get_by_board_uuid(ctx,
newwbs.board_uuid)
newwbs.create()
full_zone_domain = en_webservice.dns + "." + en_webservice.zone
dns_domain = newwbs.name + "." + full_zone_domain
return serializer.serialize_entity(ctx, newwbs)
create_record_dns_webservice(ctx, board, newwbs.name,
en_webservice.dns,
en_webservice.zone)
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()
result = manage_result(res, "ExposeWebservice", board.uuid)
LOG.info(result)
#return result
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():
list_webs = objects.Webservice.list(ctx,
filters={
'board_uuid': wbsrv.board_uuid
})
full_zone_domain = en_webservice.dns + "." + en_webservice.zone
dns_domain = wbsrv.name + "." + full_zone_domain
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_webs = objects.Webservice.list(ctx,
filters={
'board_uuid': wbsrv.board_uuid
})
try:
# result =
self.execute_on_board(ctx,
wbsrv.board_uuid,
'UnexposeWebservice',
(dns_domain, list_dns,))
except exception:
return exception
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]
if board.agent == None:
raise exception.BoardInvalidStatus(uuid=board.uuid,
status=board.status)
try:
# result =
self.execute_on_board(ctx,
wbsrv.board_uuid,
'UnexposeWebservice',
(dns_domain, list_dns,))
except exception:
return exception
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')
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')
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()
mreq = new_req(ctx, board, objects.request.BOARD,
"enable_webservice", pending_requests=0)
LOG.info(" - request: " + str(mreq.uuid))
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)
res = new_res(ctx, board, mreq.uuid)
LOG.debug('Save webservice data %s for board %s', dns + "." + zone,
board.uuid)
en_webservice.create()
en_webservice = objects.enabledwebservice.EnabledWebservice.get_by_board_uuid(ctx, board.uuid)
LOG.debug('Open ports on WampAgent %s for http and %s for https '
'on board %s', http_port, https_port, board.uuid)
LOG.info("Webservice and dns already enabled for this device:")
full_zone_domain = en_webservice.dns + "." + en_webservice.zone
service = objects.Service.get_by_name(ctx, 'webservice')
LOG.info(" --> " + str(full_zone_domain))
res = self.execute_on_board(ctx, board.uuid, "ServiceEnable",
(service, http_port,), main_req=mreq.uuid)
result = manage_result(res, "ServiceEnable", board.uuid)
msg="Webservice and dns already enabled for this device!"
w_msg = wm.WampWarning(msg, req_id=mreq.uuid)
exp_data = {
'board_uuid': board_uuid,
'service_uuid': service.uuid,
'public_port': http_port,
}
exposed = objects.ExposedService(ctx, **exp_data)
exposed.create()
res.result = w_msg.result
res.message = w_msg.message
res.save()
LOG.debug(result)
mreq.status = objects.request.COMPLETED
mreq.save()
service = objects.Service.get_by_name(ctx, 'webservice_ssl')
result = manage_result(w_msg, "EnableWebservice", board.uuid)
res = self.execute_on_board(ctx, board.uuid, "ServiceEnable",
(service, https_port,), main_req=mreq.uuid)
result = manage_result(res, "ServiceEnable", board.uuid)
LOG.info(result)
exp_data = {
'board_uuid': board_uuid,
'service_uuid': service.uuid,
'public_port': https_port,
}
exposed = objects.ExposedService(ctx, **exp_data)
exposed.create()
raise exception.EnabledWebserviceAlreadyExists(enabled_webservice=full_zone_domain, req=mreq.uuid)
LOG.debug(result)
#return serializer.serialize_entity(ctx, en_webservice)
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)
else:
mreq = new_req(ctx, board, objects.request.BOARD,
"enable_webservice", pending_requests=3)
try:
self.execute_on_board(ctx,
board.uuid,
'EnableWebService',
(dns + "." + zone, email,),
main_req=mreq.uuid)
except exception:
return exception
try:
create_record_dns(ctx, board, dns, zone)
except exception:
return exception
cctx.call(ctx, 'add_redirect', board_dns=dns, zone=zone)
cctx.call(ctx, 'reload_proxy')
try:
en_webservice = objects.enabledwebservice. \
EnabledWebservice.get_by_board_uuid(ctx,
board.uuid)
return serializer.serialize_entity(ctx, en_webservice)
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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -24,7 +24,7 @@ from iotronic.objects import utils as obj_utils
ACTIONS = ['DevicePing', 'DeviceReboot', 'DeviceRestartLR',
'DeviceNetConfig', 'DeviceEcho', 'DeviceUpgradeLR',
'DevicePkgOperation']
'DevicePkgOperation', 'DeviceRestSubmit']
def is_valid_action(action):
@ -132,6 +132,22 @@ class Board(base.IotronicObject):
board = Board._from_db_object(cls(context), db_board)
return board
@base.remotable_classmethod
def exists_by_name(cls, context, name):
"""Find a board based on name and return a boolean.
:param name: the logical name of a board.
:returns: a boolean object.
"""
res=False
try:
db_board = cls.dbapi.get_board_by_name(name)
res = True
except exception.BoardNotFound as E:
return res
return res
@base.remotable_classmethod
def list(cls, context, limit=None, marker=None, sort_key=None,
sort_dir=None, filters=None):

View File

@ -88,6 +88,46 @@ class EnabledWebservice(base.IotronicObject):
db_enabled_webservice)
return en_webserv
@base.remotable_classmethod
def isWebserviceEnabled(cls, context, uuid):
"""Find a enabled_webservice based on uuid and return a Board object.
:param uuid: the uuid of a enabled_webservice.
:returns: a boolean object.
"""
res=False
try:
db_enabled_webservice = cls.dbapi.get_enabled_webservice_by_board_uuid(
uuid)
res = True
except exception.EnabledWebserviceNotFound as E:
return res
return res
@base.remotable_classmethod
def checkDnsAvailable(cls, context, dns):
"""Check if a dns is already assigned.
:param dns: dns chosen to enabled_webservice into a device.
:returns: a boolean object.
"""
res=False
try:
db_enabled_webservice = cls.dbapi.get_enabled_webservice_by_name(
dns)
if db_enabled_webservice == False:
# OK dns approved
res = True
else:
res = False
except exception.EnabledWebserviceAlreadyExists as E:
return res
return res
@base.remotable_classmethod
def list(cls, context, limit=None, marker=None, sort_key=None,
sort_dir=None, filters=None):

View File

@ -204,6 +204,7 @@ class Plugin(base.IotronicObject):
"""
current = self.__class__.get_by_uuid(self._context, self.uuid)
for field in self.fields:
if (hasattr(self, base.get_attrname(field))
and self[field] != current[field]):
self[field] = current[field]
if (hasattr(
self, base.get_attrname(field))
and self[field] != current[field]):
self[field] = current[field]

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import asyncio
import json
import subprocess
import time
import txaio
@ -72,6 +73,12 @@ wamp_opts = [
cfg.IntOpt('autoPingTimeout',
default=2,
help=('autoPingInterval parameter for wamp')),
cfg.BoolOpt('service_allow_list',
default=False,
help='Enable service allow list checks.'),
cfg.StrOpt('service_allow_list_path',
default="(/var/lib/wstun/allowlist)",
help='Path of allowlist.json file.'),
]
@ -121,6 +128,20 @@ class WampEndpoint(object):
return r.result()
def read_allowlist():
try:
with open(CONF.wamp.service_allow_list_path, "r") as allow_file:
allow_list_str = allow_file.read()
allow_list = json.loads(allow_list_str)
#LOG.debug(allow_list)
return allow_list
except Exception as err:
LOG.error(err)
class AgentEndpoint(object):
@ -140,6 +161,54 @@ class AgentEndpoint(object):
return 1
def addin_allowlist(self, ctx, device, port):
try:
allow_list = read_allowlist()
new_node={}
new_node['client']=device
new_node['port']=str(port)
if new_node in allow_list:
LOG.warning("This device already exposes this port!")
else:
allow_list.append(new_node)
with open(CONF.wamp.service_allow_list_path, "r+") as allow_file:
allow_file.seek(0)
allow_file.write("%s" % json.dumps(allow_list))
allow_file.truncate()
read_allowlist()
LOG.debug("Added device/service port in allow list.")
except Exception as err:
print(err)
def remove_from_allowlist(self, ctx, device, port):
try:
allow_list = read_allowlist()
new_node={}
new_node['client']=device
new_node['port']=str(port)
if new_node in allow_list:
allow_list.remove(new_node)
with open(CONF.wamp.service_allow_list_path, "r+") as allow_file:
allow_file.seek(0)
allow_file.write("%s" % json.dumps(allow_list))
allow_file.truncate()
LOG.debug("Removed device/service port from allow list.")
except Exception as err:
print(err)
class RPCServer(Thread):
def __init__(self):
# AMQP CONFIG

View File

@ -23,7 +23,10 @@ LOG = logging.getLogger(__name__)
nginx_opts = [
cfg.StrOpt('nginx_path',
default='/etc/nginx/conf.d/iotronic',
help=('Default Nginx Path'))
help=('Default Nginx Path')),
cfg.StrOpt('wstun_endpoint',
default='localhost',
help=('Default Nginx Path'))
]
CONF = cfg.CONF
@ -39,9 +42,9 @@ def save_map(board, zone):
def save_upstream(board, https_port):
fp = CONF.nginx.nginx_path + "/upstreams/upstream_" + board
string = '''upstream {0} {{
server localhost:{1} max_fails=3 fail_timeout=10s;
server {2}:{1} max_fails=3 fail_timeout=10s;
}}
'''.format(board, https_port)
'''.format(board, https_port, CONF.nginx.wstun_endpoint )
with open(fp, "w") as text_file:
text_file.write("%s" % string)
@ -54,10 +57,10 @@ def save_server(board, http_port, zone):
server_name .{0}.{2};
location / {{
proxy_pass http://localhost:{1};
proxy_pass http://{3}:{1};
}}
}}
'''.format(board, http_port, zone)
'''.format(board, http_port, zone, CONF.nginx.wstun_endpoint)
with open(fp, "w") as text_file:
text_file.write("%s" % string)