Commit 0ee8fd7d authored by Nicolas Wavrant's avatar Nicolas Wavrant

recipe: new recipe to get a free network port

parent b2e8c6f2
......@@ -111,6 +111,7 @@ setup(name=name,
'erp5testnode = slapos.recipe.erp5testnode:Recipe',
'firefox = slapos.recipe.firefox:Recipe',
'fontconfig = slapos.recipe.fontconfig:Recipe',
'free_port = slapos.recipe.free_port:Recipe',
'generate.mac = slapos.recipe.generatemac:Recipe',
'generate.password = slapos.recipe.generatepassword:Recipe',
'generic.cloudooo = slapos.recipe.generic_cloudooo:Recipe',
......
##############################################################################
#
# Copyright (c) 2016 Vifib SARL and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import socket
import netaddr
class Recipe(object):
"""
Uses the socket python standard library to get an unused port.
Notice : this recipe may still fail because of race condition : if a new
process spawns and use the picked port before the service for which it has
been generated starts, then the service won't start. Therefore, the result
would be the same giving an already-in-use port to the service.
"""
def __init__(self, buildout, name, options):
self.minimum = int(options.get('minimum', 1024))
self.maximum = int(options.get('maximum', 49151))
self.ip = options.get('ip')
self.options = options.copy()
if netaddr.valid_ipv4(self.ip):
self.inet_family = socket.AF_INET
elif netaddr.valid_ipv6(self.ip):
self.inet_family = socket.AF_INET6
else:
# address family is unknown, so let's return a general purpose port
self.options['port'] = 0
return
self.options['port'] = self._getFreePort()
def _getFreePort(self):
"""
Port number will be picked from a given range, smaller port first, then
incremented until a free one is found.
This algorithm thus returns always the same value with the same parameters in
a standard environment.
"""
for port in xrange(self.minimum, self.maximum):
sock = socket.socket(self.inet_family, socket.SOCK_STREAM)
try:
sock.bind((self.ip, port))
break
except socket.error:
continue
finally:
sock.close()
else:
port = 0
return port
def install(self):
pass
def update(self):
pass
import mock
import socket
import unittest
from slapos.recipe import free_port
class SocketMock():
def __init__(self, *args, **kw):
self.args = args
self.kw = kw
pass
def nothing_happen(self, *args, **kw):
pass
bind = close = nothing_happen
import sys
sys.modules['socket'].socket = SocketMock
class FreePortTest(unittest.TestCase):
def afterSetup(self):
SocketMock.bind = SocketMock.close = SocketMock.nothing_happen
def new_recipe(self, **kw):
buildout = {
'buildout': {
'bin-directory': '',
'find-links': '',
'allow-hosts': '',
'develop-eggs-directory': '',
'eggs-directory': '',
'python': 'testpython',
},
'testpython': {
'executable': sys.executable,
},
'slap-connection': {
'computer-id': '',
'partition-id': '',
'server-url': '',
'software-release-url': '',
}
}
options = {
'ip': '127.0.0.1',
}
options.update(kw)
return free_port.Recipe(buildout=buildout, name='free_port', options=options)
def test_ifNoBusyPortThenMinPortIsAlwaysReturned(self):
recipe = self.new_recipe(minimum=2000)
self.assertEqual(recipe.options['port'], 2000)
def test_iterateUntilFreePortIsFound(self):
def bindFailExceptOnPort2020(socket_instance, binding):
ip, port = binding
if port != 2020:
raise socket.error()
SocketMock.bind = bindFailExceptOnPort2020
recipe = self.new_recipe(minimum=2000)
self.assertEqual(recipe.options['port'], 2020)
def test_returnsPort0IfNoPortIsFreeInRange(self):
def bindAlwaysFail(socket_instance, binding):
raise socket.error()
SocketMock.bind = bindAlwaysFail
recipe = self.new_recipe(minimum=2000, maximum=2100)
self.assertEqual(recipe.options['port'], 0)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment