diff --git a/etc/iotronic/iotronic.conf b/etc/iotronic/iotronic.conf index 66256a9..ff320ce 100644 --- a/etc/iotronic/iotronic.conf +++ b/etc/iotronic/iotronic.conf @@ -15,6 +15,10 @@ auth_strategy=keystone # value) #pecan_debug=false +[conductor] +service_port_min=50000 +service_port_max=60000 + [wamp] wamp_transport_url = ws://:/ diff --git a/iotronic/common/exception.py b/iotronic/common/exception.py index 5f48412..0880005 100644 --- a/iotronic/common/exception.py +++ b/iotronic/common/exception.py @@ -616,6 +616,10 @@ class ServiceAlreadyExposed(Conflict): message = _("A Service with UUID %(uuid)s already exposed.") +class NotEnoughPortForService(NotFound): + message = _("No ports available") + + class ExposedServiceNotFound(NotFound): message = _("ExposedService %(uuid)s could not be found.") diff --git a/iotronic/conductor/endpoints.py b/iotronic/conductor/endpoints.py index ffff503..8a7fb56 100644 --- a/iotronic/conductor/endpoints.py +++ b/iotronic/conductor/endpoints.py @@ -43,6 +43,8 @@ serializer = objects_base.IotronicObjectSerializer() Port = list() +SERVICE_PORT_LIST = None + def versionCompare(v1, v2): """Method to compare two versions. @@ -104,8 +106,24 @@ def get_best_agent(ctx): return agent.hostname -def random_public_port(): - return random.randint(50001, 59999) +def random_public_port(ctx=None): + global SERVICE_PORT_LIST + if not SERVICE_PORT_LIST: + # empty, create a cache list + min = cfg.CONF.conductor.service_port_min + 1 + max = cfg.CONF.conductor.service_port_max - 1 + LOG.debug('recreate service port list cache: min %i max %i', min, max) + full_list = (range(min, max)) + exp_list = objects.ExposedService.get_all_ports(ctx) + SERVICE_PORT_LIST = list(set(full_list) - set(exp_list)) + + if len(SERVICE_PORT_LIST) == 0: + LOG.warning('No more ports available') + return None + else: + num = random.choice(SERVICE_PORT_LIST) + SERVICE_PORT_LIST.remove(num) + return num def manage_result(res, wamp_rpc_call, board_uuid): @@ -477,6 +495,9 @@ class ConductorEndpoint(object): except Exception: public_port = random_public_port() + if not public_port: + return exception.NotEnoughPortForService() + res = self.execute_on_board(ctx, board_uuid, action, (service, public_port)) result = manage_result(res, action, board_uuid) @@ -504,6 +525,11 @@ class ConductorEndpoint(object): result = manage_result(res, action, board_uuid) LOG.debug(res.message) + global SERVICE_PORT_LIST + try: + SERVICE_PORT_LIST.append(exposed.public_port) + except exception: + pass exposed.destroy() return result @@ -799,9 +825,10 @@ class ConductorEndpoint(object): except Exception: - # TO BE CHANGED 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, @@ -918,6 +945,11 @@ class ConductorEndpoint(object): res = self.execute_on_board(ctx, board.uuid, "ServiceDisable", (service,), main_req=mreq.uuid) LOG.debug(res.message) + global SERVICE_PORT_LIST + try: + SERVICE_PORT_LIST.append(exposed.public_port) + except exception: + pass exposed.destroy() try: diff --git a/iotronic/conductor/manager.py b/iotronic/conductor/manager.py index ba8db9c..101a0a3 100644 --- a/iotronic/conductor/manager.py +++ b/iotronic/conductor/manager.py @@ -40,6 +40,12 @@ conductor_opts = [ help='Maximum time (in seconds) since the last check-in ' 'of a conductor. A conductor is considered inactive ' 'when this time has been exceeded.'), + cfg.IntOpt('service_port_min', + default=50000, + help='Min value for genereting random ports for services'), + cfg.IntOpt('service_port_max', + default=60000, + help='Max value for genereting random ports for services'), ] CONF = cfg.CONF diff --git a/iotronic/db/sqlalchemy/api.py b/iotronic/db/sqlalchemy/api.py index f208dc1..367e34b 100644 --- a/iotronic/db/sqlalchemy/api.py +++ b/iotronic/db/sqlalchemy/api.py @@ -911,9 +911,13 @@ class Connection(api.Connection): raise exception.ExposedServiceNotFound() def get_exposed_service_list(self, board_uuid): - query = model_query( - models.ExposedService).filter_by( - board_uuid=board_uuid) + if board_uuid: + query = model_query( + models.ExposedService).filter_by( + board_uuid=board_uuid) + else: + query = model_query( + models.ExposedService) return query.all() def _do_update_exposed_service(self, service_id, values): diff --git a/iotronic/objects/exposedservice.py b/iotronic/objects/exposedservice.py index 0c75aa6..8ed0d5a 100644 --- a/iotronic/objects/exposedservice.py +++ b/iotronic/objects/exposedservice.py @@ -31,6 +31,14 @@ class ExposedService(base.IotronicObject): 'public_port': int } + @base.remotable_classmethod + def get_all_ports(cls, context): + ls = cls.list(context) + ports = [] + for x in ls: + ports.append(x.public_port) + return ports + @staticmethod def _from_db_object(exposed_service, db_exposed_service): """Converts a database entity to a formal object.""" @@ -96,7 +104,7 @@ class ExposedService(base.IotronicObject): return exp_service @base.remotable_classmethod - def list(cls, context, board_uuid): + def list(cls, context, board_uuid=None): """Return a list of ExposedService objects. :param context: Security context. diff --git a/tox.ini b/tox.ini index c77a23a..99020df 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 2.3.1 -envlist = py3,pep8 +envlist = py35,pep8 skipsdist = True [testenv] @@ -22,8 +22,8 @@ commands = basepython = python2.7 commands = flake8 {posargs} -[testenv:py3] -basepython = python3 +[testenv:py35] +basepython = python3.5 [flake8]