Commit 5e9ee4c7 authored by Jérome Perrin's avatar Jérome Perrin

test

parent b4ef089e
...@@ -29,31 +29,25 @@ import os ...@@ -29,31 +29,25 @@ import os
import shutil import shutil
import urlparse import urlparse
import tempfile import tempfile
import StringIO
import pysftp import pysftp
import utils import utils
def setUpModule():
utils.setUpModule()
def tearDownModule(): # for development: debugging logs and install Ctrl+C handler
utils.tearDownModule() if os.environ.get('DEBUG'):
# for development: log a lot and install Ctrl+C handler
if 1:
import logging import logging
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
import unittest import unittest
unittest.installHandler() unittest.installHandler()
class ProFTPdTestCase(utils.SlapOSInstanceTestCase): class ProFTPdTestCase(utils.SlapOSInstanceTestCase):
software_url_list = ( @classmethod
os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'software.cfg')), ) def getSoftwareURLList(cls):
return (os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'software.cfg')), )
def _getConnection(self, username=None, password=None, hostname=None): def _getConnection(self, username=None, password=None, hostname=None):
"""Returns a pysftp connection connected to the SFTP """Returns a pysftp connection connected to the SFTP
...@@ -62,13 +56,12 @@ class ProFTPdTestCase(utils.SlapOSInstanceTestCase): ...@@ -62,13 +56,12 @@ class ProFTPdTestCase(utils.SlapOSInstanceTestCase):
instance connection parameters. instance connection parameters.
another hostname can also be passed. another hostname can also be passed.
""" """
# this will not verify host key # this tells paramiko not to verify host key
cnopts = pysftp.CnOpts() cnopts = pysftp.CnOpts()
cnopts.hostkeys = None cnopts.hostkeys = None
parameter_dict = self.computer_partition.getConnectionParameterDict() parameter_dict = self.computer_partition.getConnectionParameterDict()
sftp_url = urlparse.urlparse(parameter_dict['url']) sftp_url = urlparse.urlparse(parameter_dict['url'])
print parameter_dict
return pysftp.Connection( return pysftp.Connection(
hostname or sftp_url.hostname, hostname or sftp_url.hostname,
...@@ -78,9 +71,32 @@ class ProFTPdTestCase(utils.SlapOSInstanceTestCase): ...@@ -78,9 +71,32 @@ class ProFTPdTestCase(utils.SlapOSInstanceTestCase):
password=password or parameter_dict['password']) password=password or parameter_dict['password'])
class TestSFTPFeature(ProFTPdTestCase): class TestSFTPListen(ProFTPdTestCase):
"""Tests the features we expect in SFTP server. def test_listen_on_ipv4(self):
self.assertTrue(self._getConnection(hostname=self.config['ipv4_address']))
def test_does_not_listen_on_all_ip(self):
from paramiko.ssh_exception import SSHException
with self.assertRaises(SSHException):
self._getConnection(hostname='127.0.0.1')
import pdb; pdb.set_trace()
class TestSFTPOperations(ProFTPdTestCase):
"""Tests upload / download features we expect in SFTP server.
""" """
def setUp(self):
self.upload_dir = os.path.join(
self.computer_partition_root_path, 'srv', 'proftpd')
def tearDown(self):
for name in os.listdir(self.upload_dir):
path = os.path.join(self.upload_dir, name)
if os.path.isfile(path) or os.path.islink(path):
os.remove(path)
else:
shutil.rmtree(path)
def test_simple_sftp_session(self): def test_simple_sftp_session(self):
with self._getConnection() as sftp: with self._getConnection() as sftp:
# put a file # put a file
...@@ -100,30 +116,41 @@ class TestSFTPFeature(ProFTPdTestCase): ...@@ -100,30 +116,41 @@ class TestSFTPFeature(ProFTPdTestCase):
with open(local_file) as f: with open(local_file) as f:
self.assertEqual(f.read(), "Hello FTP !") self.assertEqual(f.read(), "Hello FTP !")
def test_connect_on_ipv4(self): def test_uploaded_file_not_visible_until_fully_uploaded(self):
self.assertEqual( test_self = self
[], class PartialFile(StringIO.StringIO):
self._getConnection(hostname=self.config['ipv4_address']).listdir()) def read(self, *args):
# file is not visible yet
test_self.assertNotIn('destination.', os.listdir(test_self.upload_dir))
# it's just a hidden file
test_self.assertEqual(['.in.destination.'], os.listdir(test_self.upload_dir))
return StringIO.StringIO.read(self, *args)
def test_proftpd_does_not_listen_on_all_ip(self): with self._getConnection() as sftp:
from paramiko.ssh_exception import SSHException sftp.sftp_client.putfo(PartialFile("content"), "destination")
with self.assertRaises(SSHException):
self._getConnection(hostname='127.0.0.1')
import pdb; pdb.set_trace()
def test_uploaded_file_not_visible_until_fully_uploaded(self): # now file is visible
pass self.assertEqual(['destination'], os.listdir(self.upload_dir))
def test_partial_upload_are_deleted(self): def test_partial_upload_are_deleted(self):
pass class ErrorFile(StringIO.StringIO):
def read(self, *args):
raise IOError("Something bad happened")
def test_user_can_be_added(self): with self._getConnection() as sftp:
pass with self.assertRaises(IOError):
sftp.sftp_client.putfo(ErrorFile(), "destination")
self.assertEqual([], os.listdir(self.upload_dir))
class TestUserManagement(ProFTPdTestCase):
def test_user_can_be_added_from_script(self):
raise NotImplementedError()
class TestBan(ProFTPdTestCase): class TestBan(ProFTPdTestCase):
def test_client_are_banned_after_5_wrong_passwords(self): def test_client_are_banned_after_5_wrong_passwords(self):
# Simulate 5 login errors # Simulate failed 5 login attempts
from paramiko.ssh_exception import AuthenticationException from paramiko.ssh_exception import AuthenticationException
for i in range(5): for i in range(5):
with self.assertRaisesRegexp(AuthenticationException, 'Authentication failed'): with self.assertRaisesRegexp(AuthenticationException, 'Authentication failed'):
...@@ -139,14 +166,19 @@ class TestBan(ProFTPdTestCase): ...@@ -139,14 +166,19 @@ class TestBan(ProFTPdTestCase):
self.computer_partition_root_path, 'var', 'log', 'proftpd-ban.log')) as ban_log_file: self.computer_partition_root_path, 'var', 'log', 'proftpd-ban.log')) as ban_log_file:
self.assertRegexpMatches( self.assertRegexpMatches(
ban_log_file.readlines()[-1], ban_log_file.readlines()[-1],
'login from host .* denied due to host bane') 'login from host .* denied due to host ban')
class TestPortParameter(ProFTPdTestCase): class TestInstanceParameterPort(ProFTPdTestCase):
instance_parameter_dict = { @classmethod
'port': 10022 def getInstanceParmeterDict(cls):
} cls.free_port = utils.findFreeTCPPort(cls.config['ipv4_address'])
# def test_c(self): return {'port': cls.free_port, }
# print self.id()
def test_instance_parameter_port(self):
parameter_dict = self.computer_partition.getConnectionParameterDict()
sftp_url = urlparse.urlparse(parameter_dict['url'])
self.assertEqual(self.free_port, sftp_url.port)
self.assertTrue(self._getConnection())
...@@ -27,76 +27,89 @@ ...@@ -27,76 +27,89 @@
import unittest import unittest
import os import os
import socket
from contextlib import closing
import logging
import erp5.util.testnode.SlapOSControler from erp5.util.testnode.SlapOSControler import SlapOSControler
from erp5.util.testnode.ProcessManager import ProcessManager from erp5.util.testnode.ProcessManager import ProcessManager
import slapos import slapos
process_manager = ProcessManager() logger = logging.getLogger(__name__)
def findFreeTCPPort(ip=''):
"""Find a free TCP port to listen to.
inspired by https://stackoverflow.com/a/45690594
"""
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
s.bind((ip, 0))
return s.getsockname()[1]
global slapos_controler class SlapOSInstanceTestCase(unittest.TestCase):
global config @classmethod
def getSoftwareURLList(cls):
"""Return URL of software releases to install.
To be defined by subclasses.
"""
raise NotImplementedError()
@classmethod
def getInstanceParmeterDict(cls):
"""Return instance parameters
To be defined by subclasses if needed
"""
return {}
@classmethod
def setUpClass(cls):
def setUpModule():
# TODO read from environ
# XXX beware of Error: Cannot open an HTTP server: socket.error reported AF_UNIX path too long # XXX beware of Error: Cannot open an HTTP server: socket.error reported AF_UNIX path too long
# This `working_directory` should not be too deep.
working_directory = os.path.join(os.path.dirname(__file__), '.slapos') working_directory = os.path.join(os.path.dirname(__file__), '.slapos')
# TODO: allow using a specific directory from envionment variable, similar
# to --save/--load in erp5 tests.
working_directory = '/tmp/slapotest/' working_directory = '/tmp/slapotest/'
if not os.path.exists(working_directory): if not os.path.exists(working_directory):
os.mkdir(working_directory) os.mkdir(working_directory)
global config cls.config = config = {
config={
"working_directory": working_directory, "working_directory": working_directory,
"slapos_directory": working_directory, "slapos_directory": working_directory,
"log_directory": working_directory, "log_directory": working_directory,
"computer_id": 'TODO', "computer_id": 'slapos.test', # XXX
'proxy_database': os.path.join(working_directory, 'proxy.db'), 'proxy_database': os.path.join(working_directory, 'proxy.db'),
'proxy_port': 5050, 'partition_reference': cls.__name__,
'partition_reference': 'test', # XXX shouldn't this by by testclass ? # TODO
'slapos_binary': '/opt/slapgrid/b0bcd9831b23f334bc85e4a193c748a0/bin/slapos', 'slapos_binary': '/opt/slapgrid/b0bcd9831b23f334bc85e4a193c748a0/bin/slapos',
} }
# TODO
# TODO: read from environment / guess ipv4 from buildout file
ipv4_address = '127.0.0.1' ipv4_address = '127.0.0.1'
config['proxy_host'] = config['ipv4_address'] = ipv4_address config['proxy_host'] = config['ipv4_address'] = ipv4_address
config['ipv6_address'] = '2001:67c:1254:26::3318' config['ipv6_address'] = '2001:67c:1254:26::3318'
config['proxy_port'] = findFreeTCPPort(ipv4_address)
config['master_url'] = 'http://{proxy_host}:{proxy_port}'.format(**config) config['master_url'] = 'http://{proxy_host}:{proxy_port}'.format(**config)
print config
global slapos_controler cls._process_manager = process_manager = ProcessManager()
slapos_controler = erp5.util.testnode.SlapOSControler.SlapOSControler(
# XXX this code is copied from testnode code
slapos_controler = SlapOSControler(
working_directory, working_directory,
config config
) )
print "setUpModule, starting slapos_controler", slapos_controler, "in directory", working_directory
def tearDownModule():
print "tearDownModule, terminating processes"
process_manager.killPreviousRun()
import logging
logger = logging.getLogger(__name__)
class SlapOSInstanceTestCase(unittest.TestCase):
software_url_list = (NotImplemented, )
instance_parameter_dict = {}
# This is set to the path of the instance, to inspect created files.
instance_root_path = None
@classmethod
def setUpClass(cls):
slapproxy_log = os.path.join(config['log_directory'], 'slapproxy.log') slapproxy_log = os.path.join(config['log_directory'], 'slapproxy.log')
logger.debug('Configured slapproxy log to %r', slapproxy_log) logger.debug('Configured slapproxy log to %r', slapproxy_log)
software_url_list = cls.getSoftwareURLList()
slapos_controler.initializeSlapOSControler( slapos_controler.initializeSlapOSControler(
slapproxy_log=slapproxy_log, slapproxy_log=slapproxy_log,
process_manager=process_manager, process_manager=process_manager,
reset_software=False, reset_software=False,
software_path_list=cls.software_url_list) software_path_list=software_url_list)
process_manager.supervisord_pid_file = os.path.join( process_manager.supervisord_pid_file = os.path.join(
slapos_controler.instance_root, 'var', 'run', 'supervisord.pid') slapos_controler.instance_root, 'var', 'run', 'supervisord.pid')
...@@ -105,29 +118,37 @@ class SlapOSInstanceTestCase(unittest.TestCase): ...@@ -105,29 +118,37 @@ class SlapOSInstanceTestCase(unittest.TestCase):
# TODO: log more details in this case # TODO: log more details in this case
assert software_status_dict['status_code'] == 0 assert software_status_dict['status_code'] == 0
instance_parameter_dict = cls.getInstanceParmeterDict()
instance_status_dict = slapos_controler.runComputerPartition( instance_status_dict = slapos_controler.runComputerPartition(
config, config,
cluster_configuration=cls.instance_parameter_dict, cluster_configuration=instance_parameter_dict,
environment=os.environ) environment=os.environ)
# TODO: log more details in this case # TODO: log more details in this case
assert software_status_dict['status_code'] == 0 assert software_status_dict['status_code'] == 0
cls.slapos_controler = slapos_controler # FIXME: similar to test node, only one (root) partition is really supported for now.
cls.config = config computer_partition_list = []
cls.computer_partition = slapos_controler.slap.registerOpenOrder().request( for i in range(len(software_url_list)):
cls.software_url_list[0], computer_partition_list.append(
partition_reference='testing partition 0', slapos_controler.slap.registerOpenOrder().request(
partition_parameter_kw=cls.instance_parameter_dict) software_url_list[i],
# XXX this is how testnode name created partitions
partition_reference='testing partition {i}'.format(i=i, **config),
partition_parameter_kw=instance_parameter_dict))
# expose some class attributes so that tests can use them:
# the ComputerPartition instances, to getInstanceParmeterDict
cls.computer_partition = computer_partition_list[0]
# the path of the instance on the filesystem, for low level inspection
cls.computer_partition_root_path = os.path.join( cls.computer_partition_root_path = os.path.join(
config['working_directory'], config['working_directory'],
'inst', 'inst',
cls.computer_partition.getId()) cls.computer_partition.getId())
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
print "Teardown", cls print "Teardown", cls
#for reference in cls.slapos_controler.instance_config: # FIXME: if setUpClass fail, this is not called and leaks zombie processes
# cls.slapos_controler.destroyInstance(reference) cls._process_manager.killPreviousRun()
cls.slapos_controler.process_manager.killPreviousRun()
import os
#os.waitpid(0, 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