3

Is it possible to setup SFTP (SSH File Transfer Protocol) on temp folder? I want this for testing purposes (first setup SFTP and after tests tear it down).

Or maybe there is some mock approach where I could mimic as if SFTP exists on that temp folder?

Something like:

import tempfile
import unittest


class TestSFTP(unittest.TestCase):

    @classmethod
    def setupClass(cls):
        folder = tempfile.TemporaryDirectory()
        # use temp folder and setup temp sftp to be used in tests

    # define some test methods and use sftp.

    @classmethod
    def tearDownClass(cls):
        # destroy temp sftp.

P.S. Normally to create SFTP, you need to use sudo, restart some services etc, so such approach would be unfeasible for testing purposes.

Update:

So I was able to set up test class, that it would run sftp server, but I've got issues when I need to stop sftp server properly. Here is the code I've got so far..:

import sftpserver
import paramiko
import os
import sh
import threading

from odoo.tests import common
from odoo.modules.module import get_module_path


def _start_thread(target, args=None, kwargs=None):
    """Run target object in thread, in a background."""
    if not args:
        args = []
    if not kwargs:
        kwargs = {}
    thread = threading.Thread(target=target, args=args, kwargs=kwargs)
    thread.daemon = True
    thread.start()
    return thread


class TestExchangeCommon(common.SavepointCase):
    """Common class for exchange module tests."""

    tests_path = os.path.join(get_module_path('exchange'), 'tests')
    rsa_key_path = os.path.join(tests_path, 'extra/test_rsa.key')

    @classmethod
    def _start_sftp(cls):
        sftpserver.start_server('localhost', 3373, cls.rsa_key_path, 'INFO')

    @classmethod
    def setUpClass(cls):
        """Set up data to be used by all test classes."""
        import logging
        _logger = logging.getLogger()
        super(TestExchangeCommon, cls).setUpClass()
        cls.thread = _start_thread(target=cls._start_sftp)
        pkey = paramiko.RSAKey.from_private_key_file(cls.rsa_key_path)
        cls.transport = paramiko.Transport(('localhost', 3373))
        cls.transport.connect(username='admin', password='admin', pkey=pkey)
        cls.sftp = paramiko.SFTPClient.from_transport(cls.transport)
        _logger.warn(cls.sftp.listdir('.'))

    @classmethod
    def tearDownClass(cls):
        """Kill sftp server to stop running it in a background."""
        cls.sftp.close()
        cls.transport.close()
        sh.fuser('-k', '3373/tcp')  # this kills main application...

In order for sftp server to run, I had to put in a thread, so it would not stall my main application. But when I need to stop sftp server after tests are done, if I kill on port 3373 (sftp server is run), it kills main application too, which is actually run on port 8069. Is there a way to close sftpserver instance properly via python?

2
  • literally the first thing in google pypi.org/project/sftpserver Commented Jan 29, 2019 at 8:40
  • @AntiMatterDynamite Thanks, will check that out. Somehow I did not find it. Maybe my search was too specific..:) Commented Jan 29, 2019 at 8:51

1 Answer 1

0

Here is my implementation for sftpserver setup that can be used in unittests.

With threading, it was problematic to stop sftpserver, but with multiprocessing it does seem to work properly.

Also note that I added sleep just after process is started, because at least in my case, sometimes sftp server would not start in time and I would get an error that connection was refused.

(Paths implementation for pkey and sftpserver location might be different in different application though):

import sftpserver
import paramiko
import os
import sh
import multiprocessing
import time

from odoo.tests import common
from odoo.modules.module import get_module_path


def _start_process(target, args=None, kwargs=None):
    """Run target object in new process, in a background."""
    if not args:
        args = []
    if not kwargs:
        kwargs = {}
    process = multiprocessing.Process(target=target, args=args, kwargs=kwargs)
    process.daemon = True
    process.start()
    return process


class TestExchangeCommon(common.SavepointCase):
    """Common class for exchange module tests."""

    tests_path = os.path.join(get_module_path('exchange'), 'tests')
    rsa_key_path = os.path.join(tests_path, 'extra/test_rsa.key')

    @classmethod
    def _start_sftp(cls):
        sftpserver.start_server('localhost', 3373, cls.rsa_key_path, 'INFO')

    @classmethod
    def setUpClass(cls):
        """Set up data to be used by all test classes."""
        import logging
        _logger = logging.getLogger()
        super(TestExchangeCommon, cls).setUpClass()
        cls.process = _start_process(target=cls._start_sftp)
        time.sleep(0.01)  # give time for server to start up.
        pkey = paramiko.RSAKey.from_private_key_file(cls.rsa_key_path)
        cls.transport = paramiko.Transport(('localhost', 3373))
        cls.transport.connect(username='admin', password='admin', pkey=pkey)
        cls.sftp = paramiko.SFTPClient.from_transport(cls.transport)
        _logger.warn(cls.sftp.listdir('.'))

    @classmethod
    def tearDownClass(cls):
        """Kill sftp server to stop running it in a background."""
        cls.sftp.close()
        cls.transport.close()
        sh.fuser('-k', '3373/tcp')
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.