# Copyright (c) 2015-2016 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from contextlib import contextmanager
import errno
import unittest
from unittest import mock

from storlets.sbus import command as sbus_cmd
from storlets.sbus.client import SBusResponse
from storlets.sbus.client.exceptions import SBusClientSendError

from storlets.agent.daemon_factory.server import SDaemonError, \
    StorletDaemonFactory

from tests.unit import FakeLogger
from tests.unit.agent.common import test_server


class DummyDatagram(object):
    def __init__(self, prms=None):
        self.params = prms or {}


class TestStorletDaemonFactory(unittest.TestCase):
    # Class paths used in mock.patch
    base_path = 'storlets.agent.daemon_factory.server'
    os_environ_path = base_path + '.os.environ'
    os_path_exists_path = base_path + '.os.path.exists'
    os_access_path = base_path + '.os.access'
    os_kill_path = base_path + '.os.kill'
    os_waitpid_path = base_path + '.os.waitpid'
    subprocess_popen_path = base_path + '.subprocess.Popen'
    time_sleep_path = base_path + '.time.sleep'

    @contextmanager
    def _mock_sbus_client(self, method):
        sbusclient_path = self.base_path + '.SBusClient'
        with mock.patch('.'.join([sbusclient_path, method])) as _method:
            yield _method

    def setUp(self):
        self.logger = FakeLogger()
        self.pipe_path = 'path/to/pipe'
        self.container_id = 'contid'
        self.dfactory = StorletDaemonFactory(self.pipe_path, self.logger,
                                             self.container_id)

    def test_get_jvm_args(self):
        dummy_env = {'CLASSPATH': '/default/classpath',
                     'LD_LIBRARY_PATH': '/default/ld/library/path'}
        with mock.patch(self.os_environ_path, dummy_env):
            pargs, env = self.dfactory.get_jvm_args(
                'java', 'path/to/storlet/a', 'Storlet-1.0.jar',
                1, 'path/to/uds/a', 'DEBUG')
            self.assertEqual(
                ['/usr/bin/java', 'org.openstack.storlet.daemon.SDaemon',
                 'Storlet-1.0.jar', 'path/to/uds/a', 'DEBUG', '1',
                 self.container_id],
                pargs)

            self.assertIn('CLASSPATH', env)
            self.assertEqual(
                ['/default/classpath',
                 '/usr/local/lib/storlets/java/*',
                 '/usr/local/lib/storlets/java/',
                 'path/to/storlet/a'],
                env['CLASSPATH'].split(':'))

            self.assertIn('LD_LIBRARY_PATH', env)
            self.assertEqual(
                ['/default/ld/library/path',
                 '/usr/local/lib/storlets',
                 '/usr/local/lib/storlets/java'],
                env['LD_LIBRARY_PATH'].split(':'))

    def test_get_python_args(self):
        self._test_get_python_args(None, '3')
        self._test_get_python_args('3.10', '3.10')
        self._test_get_python_args('3', '3')

    def _test_get_python_args(self, version, expected):
        dummy_env = {'PYTHONPATH': '/default/pythonpath'}
        with mock.patch(self.os_environ_path, dummy_env):
            pargs, env = self.dfactory.get_python_args(
                'python', 'path/to/storlet', 'test_storlet.TestStorlet',
                1, 'path/to/uds', 'DEBUG', version)
        self.assertEqual(
            ['/usr/bin/python%s' % expected,
             '/usr/local/libexec/storlets/storlets-daemon',
             'test_storlet.TestStorlet',
             'path/to/uds', 'DEBUG', '1', self.container_id],
            pargs)
        self.assertEqual(
            {'PYTHONPATH': '/default/pythonpath:'
                           '/home/swift/test_storlet.TestStorlet'},
            env)

    def test_spawn_subprocess(self):
        self.dfactory.storlet_name_to_pipe_name = \
            {'storleta': 'path/to/uds/a'}

        class FakePopenObject(object):
            def __init__(self, pid):
                self.pid = pid
                self.stderr = mock.MagicMock()

        with mock.patch(self.os_path_exists_path) as exists, \
                mock.patch(self.os_access_path) as access, \
                mock.patch(self.subprocess_popen_path) as popen, \
                mock.patch(self.time_sleep_path), \
                mock.patch(self.os_waitpid_path) as waitpid, \
                self._mock_sbus_client('ping') as ping:
            exists.return_value = True
            access.return_velue = True
            popen.side_effect = [FakePopenObject(1000),
                                 FakePopenObject(1001)]
            waitpid.return_value = 0, 0
            ping.return_value = SBusResponse(True, 'OK')
            self.dfactory.spawn_subprocess(
                ['arg0', 'argv1', 'argv2'],
                {'envk0': 'envv0'}, 'storleta')
            self.assertEqual(('arg0',), exists.call_args[0])
            self.assertEqual((1000, 1), waitpid.call_args[0])
            self.assertEqual({'storleta': 1000},
                             self.dfactory.storlet_name_to_pid)

        with mock.patch(self.os_path_exists_path) as exists, \
                mock.patch(self.os_access_path) as access, \
                mock.patch(self.subprocess_popen_path) as popen, \
                mock.patch(self.time_sleep_path), \
                mock.patch(self.os_waitpid_path) as waitpid, \
                self._mock_sbus_client('ping') as ping:
            exists.return_value = True
            access.return_velue = True
            popen.side_effect = [FakePopenObject(1000),
                                 FakePopenObject(1001)]
            waitpid.return_value = 0, 0
            ping.return_value = SBusResponse(False, 'NG')
            with self.assertRaises(SDaemonError):
                self.dfactory.spawn_subprocess(
                    ['arg0', 'argv1', 'argv2'],
                    {'envk0': 'envv0'}, 'storleta')
            self.assertEqual(('arg0',), exists.call_args[0])
            self.assertEqual((1000, 1), waitpid.call_args[0])
            self.assertEqual({'storleta': 1000},
                             self.dfactory.storlet_name_to_pid)

        with mock.patch(self.os_path_exists_path) as exists, \
                mock.patch(self.os_access_path) as access, \
                mock.patch(self.subprocess_popen_path) as popen, \
                mock.patch(self.time_sleep_path), \
                mock.patch(self.os_waitpid_path) as waitpid:
            exists.return_value = True
            access.return_velue = True
            popen.side_effect = [FakePopenObject(1000),
                                 FakePopenObject(1001)]
            waitpid.return_value = 1000, -1
            with self.assertRaises(SDaemonError):
                self.dfactory.spawn_subprocess(
                    ['arg0', 'argv1', 'argv2'],
                    {'envk0': 'envv0'}, 'storleta')
            self.assertEqual(('arg0',), exists.call_args[0])
            self.assertEqual((1000, 1), waitpid.call_args[0])

        with mock.patch(self.os_path_exists_path) as exists, \
                mock.patch(self.subprocess_popen_path) as popen:
            exists.return_value = False
            with self.assertRaises(SDaemonError):
                self.dfactory.spawn_subprocess(
                    ['arg0', 'argv1', 'argv2'],
                    {'envk0': 'envv0'}, 'storleta')
            self.assertEqual(('arg0',), exists.call_args[0])
            popen.assert_not_called()

        with mock.patch(self.os_path_exists_path) as exists, \
                mock.patch(self.os_access_path) as access, \
                mock.patch(self.subprocess_popen_path) as popen:
            exists.return_value = True
            access.return_value = False
            with self.assertRaises(SDaemonError):
                self.dfactory.spawn_subprocess(
                    ['arg0', 'argv1', 'argv2'],
                    {'envk0': 'envv0'}, 'storleta')
            self.assertEqual(('arg0',), exists.call_args[0])
            popen.assert_not_called()

        with mock.patch(self.os_path_exists_path) as exists, \
                mock.patch(self.os_access_path) as access, \
                mock.patch(self.subprocess_popen_path) as popen:
            exists.return_value = True
            access.return_value = False
            popen.side_effect = OSError()
            with self.assertRaises(SDaemonError):
                self.dfactory.spawn_subprocess(
                    ['arg0', 'argv1', 'argv2'],
                    {'envk0': 'envv0'}, 'storleta')
            self.assertEqual(('arg0',), exists.call_args[0])

        with mock.patch(self.os_path_exists_path) as exists:
            with self.assertRaises(SDaemonError):
                self.dfactory.spawn_subprocess([], {}, 'storleta')
            self.assertEqual(0, exists.call_count)

    def test_wait_for_daemon_to_initialize(self):
        self.dfactory.storlet_name_to_pipe_name = \
            {'storleta': 'path/to/uds/a'}

        with self._mock_sbus_client('ping') as ping, \
                mock.patch(self.time_sleep_path):
            ping.return_value = SBusResponse(True, 'OK')
            self.assertTrue(
                self.dfactory.wait_for_daemon_to_initialize('storleta'))
            self.assertEqual(1, ping.call_count)

        with self._mock_sbus_client('ping') as ping, \
                mock.patch(self.time_sleep_path):
            ping.return_value = SBusResponse(False, 'NG')
            self.assertFalse(
                self.dfactory.wait_for_daemon_to_initialize('storleta'))
            self.assertEqual(
                self.dfactory.NUM_OF_TRIES_PINGING_STARTING_DAEMON,
                ping.call_count)

        self.dfactory.storlet_name_to_pipe_name = \
            {'storleta': 'path/to/uds/a', 'storletb': 'path/to/uds/b'}
        with self._mock_sbus_client('ping') as ping, \
                mock.patch(self.time_sleep_path):
            ping.side_effect = SBusClientSendError()
            self.assertFalse(
                self.dfactory.wait_for_daemon_to_initialize('storleta'))
            self.assertEqual(
                self.dfactory.NUM_OF_TRIES_PINGING_STARTING_DAEMON,
                ping.call_count)

    def test_process_start_daemon(self):
        # Not running
        self.dfactory.storlet_name_to_pid = {}
        self.dfactory.storlet_name_to_pipe_name = {}

        class FakePopenObject(object):
            def __init__(self, pid):
                self.pid = pid
                self.stderr = mock.MagicMock()

        with mock.patch(self.os_path_exists_path) as exists, \
                mock.patch(self.os_access_path) as access, \
                mock.patch(self.subprocess_popen_path) as popen, \
                mock.patch(self.time_sleep_path), \
                mock.patch(self.os_waitpid_path) as waitpid, \
                self._mock_sbus_client('ping') as ping:
            exists.return_value = True
            access.return_value = True
            popen.side_effect = [FakePopenObject(1000),
                                 FakePopenObject(1001)]
            waitpid.return_value = 0, 0
            ping.return_value = SBusResponse(True, 'OK')
            self.assertTrue(self.dfactory.process_start_daemon(
                'java', 'path/to/storlet/a', 'storleta', 1, 'path/to/uds/a',
                'TRACE'))
            self.assertEqual({'storleta': 'path/to/uds/a'},
                             self.dfactory.storlet_name_to_pipe_name)

        # Already running
        self.dfactory.storlet_name_to_pid = {'storleta': 1000}
        self.dfactory.storlet_name_to_pipe_name = {'storleta': 'path/to/uds/a'}
        with mock.patch(self.os_waitpid_path) as waitpid:
            waitpid.return_value = 0, 0
            self.assertFalse(self.dfactory.process_start_daemon(
                'java', 'path/to/storlet/a', 'storleta', 1, 'path/to/uds/a',
                'TRACE'))

        # Unsupported language
        with self.assertRaises(SDaemonError):
            self.dfactory.process_start_daemon(
                'foo', 'path/to/storlet/a', 'storleta', 1, 'path/to/uds/a',
                'TRACE')

    def test_get_process_status_by_name(self):
        self.dfactory.storlet_name_to_pid = \
            {'storleta': 1000, 'storletb': 1001}

        with mock.patch(self.os_waitpid_path) as waitpid:
            waitpid.return_value = 0, 0
            self.assertTrue(
                self.dfactory.get_process_status_by_name('storleta'))
            self.assertEqual(1, waitpid.call_count)
            self.assertEqual((1000, 1), waitpid.call_args[0])

        self.assertFalse(
            self.dfactory.get_process_status_by_name('storletc'))

    def test_get_process_status_by_pid(self):
        with mock.patch(self.os_waitpid_path) as waitpid:
            waitpid.return_value = 0, 0
            self.assertTrue(
                self.dfactory.get_process_status_by_pid(1000, 'storleta'))
            self.assertEqual(1, waitpid.call_count)
            self.assertEqual((1000, 1), waitpid.call_args[0])

        with mock.patch(self.os_waitpid_path) as waitpid:
            waitpid.return_value = 1000, 0
            self.assertFalse(
                self.dfactory.get_process_status_by_pid(1000, 'storleta'))
            self.assertEqual(1, waitpid.call_count)
            self.assertEqual((1000, 1), waitpid.call_args[0])

        with mock.patch(self.os_waitpid_path) as waitpid:
            waitpid.side_effect = OSError(errno.ESRCH, '')
            self.assertFalse(
                self.dfactory.get_process_status_by_pid(1000, 'storleta'))
            self.assertEqual(1, waitpid.call_count)
            self.assertEqual((1000, 1), waitpid.call_args[0])

        with mock.patch(self.os_waitpid_path) as waitpid:
            waitpid.side_effect = OSError(errno.EPERM, '')
            exc_pattern = ('^No permission to access the storlet daemon'
                           ' storleta$')
            with self.assertRaisesRegex(SDaemonError, exc_pattern):
                self.dfactory.get_process_status_by_pid(1000, 'storleta')
            self.assertEqual(1, waitpid.call_count)
            self.assertEqual((1000, 1), waitpid.call_args[0])

        with mock.patch(self.os_waitpid_path) as waitpid:
            waitpid.side_effect = OSError()
            exc_pattern = '^Unknown error$'
            with self.assertRaisesRegex(SDaemonError, exc_pattern):
                self.dfactory.get_process_status_by_pid(1000, 'storleta')
            self.assertEqual(1, waitpid.call_count)
            self.assertEqual((1000, 1), waitpid.call_args[0])

    def test_process_kill(self):
        # Success
        self.dfactory.storlet_name_to_pid = \
            {'storleta': 1000, 'storletb': 1001}
        with mock.patch(self.os_kill_path) as kill, \
                mock.patch(self.os_waitpid_path) as waitpid:
            waitpid.return_value = 1000, 0
            self.assertEqual((1000, 0),
                             self.dfactory.process_kill('storleta'))
            self.assertEqual(1, kill.call_count)
            self.assertEqual(1, waitpid.call_count)
            self.assertEqual({'storletb': 1001},
                             self.dfactory.storlet_name_to_pid)

        # When failed to send kill to the storlet daemon
        self.dfactory.storlet_name_to_pid = \
            {'storleta': 1000, 'storletb': 1001}
        with mock.patch(self.os_kill_path) as kill, \
                mock.patch(self.os_waitpid_path) as waitpid:
            kill.side_effect = OSError()
            with self.assertRaises(SDaemonError):
                self.dfactory.process_kill('storleta')
            self.assertEqual(1, kill.call_count)
            self.assertEqual(0, waitpid.call_count)
            self.assertEqual({'storleta': 1000, 'storletb': 1001},
                             self.dfactory.storlet_name_to_pid)

        # When failed to wait
        self.dfactory.storlet_name_to_pid = \
            {'storleta': 1000, 'storletb': 1001}
        with mock.patch(self.os_kill_path) as kill, \
                mock.patch(self.os_waitpid_path) as waitpid:
            waitpid.side_effect = OSError()
            with self.assertRaises(SDaemonError):
                self.dfactory.process_kill('storleta')
            self.assertEqual(1, kill.call_count)
            self.assertEqual(1, waitpid.call_count)
            self.assertEqual({'storleta': 1000, 'storletb': 1001},
                             self.dfactory.storlet_name_to_pid)

        # if the storlet daemon is not recognised
        self.dfactory.storlet_name_to_pid = \
            {'storleta': 1000, 'storletb': 1001}
        with mock.patch(self.os_kill_path) as kill, \
                mock.patch(self.os_waitpid_path) as waitpid:
            with self.assertRaises(SDaemonError):
                self.dfactory.process_kill('storletc')
            self.assertEqual(0, kill.call_count)
            self.assertEqual(0, waitpid.call_count)
            self.assertEqual({'storleta': 1000, 'storletb': 1001},
                             self.dfactory.storlet_name_to_pid)

    def test_process_kill_all(self):
        # Success
        self.dfactory.storlet_name_to_pid = \
            {'storleta': 1000, 'storletb': 1001}
        with mock.patch(self.os_kill_path) as kill, \
                mock.patch(self.os_waitpid_path) as waitpid:
            waitpid.side_effect = [(1000, 0), (1001, 0)]
            self.dfactory.process_kill_all()
            self.assertEqual(2, kill.call_count)
            self.assertEqual(2, waitpid.call_count)
            self.assertEqual({}, self.dfactory.storlet_name_to_pid)

        # Success (no processes)
        self.dfactory.storlet_name_to_pid = {}
        with mock.patch(self.os_kill_path) as kill, \
                mock.patch(self.os_waitpid_path) as waitpid:
            self.dfactory.process_kill_all()
            self.assertEqual(0, kill.call_count)
            self.assertEqual(0, waitpid.call_count)
            self.assertEqual({}, self.dfactory.storlet_name_to_pid)

        # Failure (try_all = True)
        self.dfactory.storlet_name_to_pid = \
            {'storleta': 1000, 'storletb': 1001}
        with mock.patch(self.os_kill_path) as kill, \
                mock.patch(self.os_waitpid_path) as waitpid:
            kill.side_effect = OSError()
            exc_pattern = '^Failed to kill some storlet daemons: .*'
            with self.assertRaisesRegex(SDaemonError, exc_pattern) as e:
                self.dfactory.process_kill_all()
            self.assertIn('storleta', str(e.exception))
            self.assertIn('storletb', str(e.exception))
            self.assertEqual(2, kill.call_count)
            self.assertEqual(0, waitpid.call_count)
            self.assertEqual({'storleta': 1000, 'storletb': 1001},
                             self.dfactory.storlet_name_to_pid)

        # Failure (try_all = False)
        self.dfactory.storlet_name_to_pid = \
            {'storleta': 1000, 'storletb': 1001}
        with mock.patch(self.os_kill_path) as kill, \
                mock.patch(self.os_waitpid_path) as waitpid:
            kill.side_effect = OSError()
            exc_pattern = ('^Failed to send kill signal to the storlet daemon '
                           'storlet[a-b]$')
            with self.assertRaisesRegex(SDaemonError, exc_pattern):
                self.dfactory.process_kill_all(False)
            self.assertEqual(1, kill.call_count)
            self.assertEqual(0, waitpid.call_count)
            self.assertEqual({'storleta': 1000, 'storletb': 1001},
                             self.dfactory.storlet_name_to_pid)

    def test_shutdown_all_processes(self):
        # Success
        self.dfactory.storlet_name_to_pid = \
            {'storleta': 1000, 'storletb': 1001}
        self.dfactory.storlet_name_to_pipe_name = \
            {'storleta': 'path/to/uds/a', 'storletb': 'path/to/uds/b'}
        with self._mock_sbus_client('halt') as halt, \
                mock.patch(self.os_waitpid_path):
            halt.return_value = SBusResponse(True, 'OK')
            terminated = self.dfactory.shutdown_all_processes()
            self.assertEqual(2, len(terminated))
            self.assertIn('storleta', terminated)
            self.assertIn('storletb', terminated)
            self.assertEqual({},
                             self.dfactory.storlet_name_to_pid)

        # Failure (try_all = True)
        self.dfactory.storlet_name_to_pid = \
            {'storleta': 1000, 'storletb': 1001}
        self.dfactory.storlet_name_to_pipe_name = \
            {'storleta': 'patha', 'storletb': 'pathb'}
        with self._mock_sbus_client('halt') as halt, \
                mock.patch(self.os_waitpid_path) as waitpid:
            halt.side_effect = SBusClientSendError()
            exc_pattern = '^Failed to shutdown some storlet daemons: .*'
            with self.assertRaisesRegex(SDaemonError, exc_pattern) as e:
                self.dfactory.shutdown_all_processes()
            self.assertIn('storleta', str(e.exception))
            self.assertIn('storletb', str(e.exception))
            self.assertEqual(2, halt.call_count)
            self.assertEqual(0, waitpid.call_count)
            self.assertEqual({'storleta': 1000, 'storletb': 1001},
                             self.dfactory.storlet_name_to_pid)

        # Failure (try_all = False)
        self.dfactory.storlet_name_to_pid = \
            {'storleta': 1000, 'storletb': 1001}
        self.dfactory.storlet_name_to_pipe_name = \
            {'storleta': 'patha', 'storletb': 'pathb'}
        with self._mock_sbus_client('halt') as halt, \
                mock.patch(self.os_waitpid_path) as waitpid:
            halt.side_effect = SBusClientSendError()
            exc_pattern = ('^Failed to send halt command to the storlet '
                           'daemon storlet[a-b]$')
            with self.assertRaisesRegex(SDaemonError, exc_pattern):
                self.dfactory.shutdown_all_processes(False)
            self.assertEqual(1, halt.call_count)
            self.assertEqual(0, waitpid.call_count)
            self.assertEqual({'storleta': 1000, 'storletb': 1001},
                             self.dfactory.storlet_name_to_pid)

    def test_shutdown_process(self):
        # Success
        self.dfactory.storlet_name_to_pid = \
            {'storleta': 1000, 'storletb': 1001}
        self.dfactory.storlet_name_to_pipe_name = \
            {'storleta': 'path/to/uds/a', 'storletb': 'path/to/uds/b'}
        with self._mock_sbus_client('halt') as halt, \
                mock.patch(self.os_waitpid_path):
            halt.return_value = SBusResponse(True, 'OK')
            self.dfactory.shutdown_process('storleta')
            self.assertEqual({'storletb': 1001},
                             self.dfactory.storlet_name_to_pid)

        # Failed to send a command to the storlet daemon
        self.dfactory.storlet_name_to_pid = \
            {'storleta': 1000, 'storletb': 1001}
        self.dfactory.storlet_name_to_pipe_name = \
            {'storleta': 'path/to/uds/a', 'storletb': 'path/to/uds/b'}
        with self._mock_sbus_client('halt') as halt, \
                mock.patch(self.os_waitpid_path) as waitpid:
            halt.side_effect = SBusClientSendError()
            with self.assertRaises(SDaemonError):
                self.dfactory.shutdown_process('storleta')
            self.assertEqual(0, waitpid.call_count)
            self.assertEqual({'storleta': 1000, 'storletb': 1001},
                             self.dfactory.storlet_name_to_pid)

        # Failed to wait
        self.dfactory.storlet_name_to_pid = \
            {'storleta': 1000, 'storletb': 1001}
        self.dfactory.storlet_name_to_pipe_name = \
            {'storleta': 'path/to/uds/a', 'storletb': 'path/to/uds/b'}
        with self._mock_sbus_client('halt') as halt, \
                mock.patch(self.os_waitpid_path) as waitpid:
            halt.return_value = SBusResponse(True, 'OK')
            waitpid.side_effect = OSError()
            with self.assertRaises(SDaemonError):
                self.dfactory.shutdown_process('storleta')
            self.assertEqual({'storleta': 1000, 'storletb': 1001},
                             self.dfactory.storlet_name_to_pid)

        # If the storlet is not found in pid mapping
        self.dfactory.storlet_name_to_pid = \
            {'storleta': 1000, 'storletb': 1001}
        self.dfactory.storlet_name_to_pipe_name = \
            {'storleta': 'path/to/uds/a', 'storletb': 'path/to/uds/b'}
        with self.assertRaises(SDaemonError):
            self.dfactory.shutdown_process('storletc')

    def test_start_daemon(self):
        prms = {'daemon_language': 'java',
                'storlet_path': 'path/to/storlet/a',
                'storlet_name': 'storleta',
                'pool_size': 1,
                'uds_path': 'path/to/uds/a',
                'log_level': 'TRACE'}
        # Not running
        self.dfactory.storlet_name_to_pid = {}
        self.dfactory.storlet_name_to_pipe_name = {}

        class FakePopenObject(object):
            def __init__(self, pid):
                self.pid = pid
                self.stderr = mock.MagicMock()

        with mock.patch(self.os_path_exists_path) as exists, \
                mock.patch(self.os_access_path) as access, \
                mock.patch(self.subprocess_popen_path) as popen, \
                mock.patch(self.time_sleep_path), \
                mock.patch(self.os_waitpid_path) as waitpid, \
                self._mock_sbus_client('ping') as ping, \
                self._mock_sbus_client('start_daemon') as start_daemon:
            exists.return_value = True
            access.return_value = True
            popen.side_effect = [FakePopenObject(1000),
                                 FakePopenObject(1001)]
            waitpid.return_value = 0, 0
            ping.return_value = SBusResponse(True, 'OK')
            start_daemon.return_value = SBusResponse(True, 'OK')
            ret = self.dfactory.start_daemon(DummyDatagram(prms))
            self.assertTrue(ret.status)
            self.assertEqual('OK', ret.message)
            self.assertTrue(ret.iterable)

        # Already running
        self.dfactory.storlet_name_to_pid = {'storleta': 1000}
        self.dfactory.storlet_name_to_pipe_name = {'storleta': 'path/to/uds/a'}
        with mock.patch(self.os_waitpid_path) as waitpid:
            waitpid.return_value = 0, 0
            ret = self.dfactory.start_daemon(DummyDatagram(prms))
            self.assertTrue(ret.status)
            self.assertEqual(
                'The storlet daemon storleta is already running', ret.message)
            self.assertTrue(ret.iterable)

        # Unsupported language
        prms['daemon_language'] = 'foo'
        ret = self.dfactory.start_daemon(DummyDatagram(prms))
        self.assertFalse(ret.status)
        self.assertEqual('Got unsupported daemon language: foo', ret.message)
        self.assertTrue(ret.iterable)

    def test_stop_daemon(self):
        # Success
        self.dfactory.storlet_name_to_pid = \
            {'storleta': 1000}
        with mock.patch(self.os_kill_path), \
                mock.patch(self.os_waitpid_path) as waitpid:
            waitpid.return_value = 1000, 0
            resp = self.dfactory.stop_daemon(
                DummyDatagram({'storlet_name': 'storleta'}))
            self.assertTrue(resp.status)
            self.assertEqual('The storlet daemon storleta is stopped',
                             resp.message)
            self.assertTrue(resp.iterable)

        # Failure
        self.dfactory.storlet_name_to_pid = \
            {'storleta': 1000}
        with mock.patch(self.os_kill_path) as kill, \
                mock.patch(self.os_waitpid_path):
            kill.side_effect = OSError('ERROR')
            resp = self.dfactory.stop_daemon(
                DummyDatagram({'storlet_name': 'storleta'}))
            self.assertFalse(resp.status)
            self.assertEqual(
                'Failed to send kill signal to the storlet daemon storleta',
                resp.message)
            self.assertTrue(resp.iterable)

    def test_daemon_status(self):
        self.dfactory.storlet_name_to_pid = \
            {'storleta': 1000, 'storletb': 1001}

        with mock.patch(self.os_waitpid_path) as waitpid:
            waitpid.return_value = 0, 0
            resp = self.dfactory.daemon_status(
                DummyDatagram({'storlet_name': 'storleta'}))
            self.assertTrue(resp.status)
            self.assertEqual('The storlet daemon storleta seems to be OK',
                             resp.message)
            self.assertTrue(resp.iterable)

        with mock.patch(self.os_waitpid_path) as waitpid:
            waitpid.return_value = 1000, 0
            resp = self.dfactory.daemon_status(
                DummyDatagram({'storlet_name': 'storleta'}))
            self.assertFalse(resp.status)
            self.assertEqual('No running storlet daemons for storleta',
                             resp.message)
            self.assertTrue(resp.iterable)

        with mock.patch(self.os_waitpid_path) as waitpid:
            waitpid.side_effect = OSError()
            resp = self.dfactory.daemon_status(
                DummyDatagram({'storlet_name': 'storleta'}))
            self.assertFalse(resp.status)
            self.assertEqual('Unknown error', resp.message)
            self.assertTrue(resp.iterable)

    def test_halt(self):
        self.dfactory.storlet_name_to_pid = \
            {'storleta': 1000, 'storletb': 1001}
        self.dfactory.storlet_name_to_pipe_name = \
            {'storleta': 'path/to/uds/a', 'storletb': 'path/to/uds/b'}
        with self._mock_sbus_client('halt') as halt, \
                mock.patch(self.os_waitpid_path):
            halt.return_value = SBusResponse(True, 'OK')
            resp = self.dfactory.halt(DummyDatagram())
            self.assertTrue(resp.status)
            self.assertEqual(
                'Stopped all storlet daemons. Terminating.', resp.message)
            self.assertFalse(resp.iterable)

    def test_stop_daemons(self):
        # Success
        self.dfactory.storlet_name_to_pid = \
            {'storleta': 1000, 'storletb': 1001}
        with mock.patch(self.os_kill_path), \
                mock.patch(self.os_waitpid_path) as waitpid:
            waitpid.side_effect = [(1000, 0), (1001, 0)]
            resp = self.dfactory.stop_daemons(DummyDatagram())
            self.assertTrue(resp.status)
            self.assertEqual('OK', resp.message)
            self.assertFalse(resp.iterable)


class TestSBusServerMain(test_server.TestSBusServerMain):

    def _get_test_server(self):
        return StorletDaemonFactory(self.sbus_path, self.logger, 'contid')

    def test_main_loop_successful_stop(self):
        # SBUS_CMD_HALT is for working to stop requested from
        # storlet_middleware
        self._test_main_loop_stop(sbus_cmd.SBUS_CMD_HALT)

    def test_main_loop_timeout(self):
        self._test_main_loop_timeout()


if __name__ == '__main__':
    unittest.main()
