# 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 unittest import mock

from oslo_config import cfg
from oslo_log import log as logging
import testtools

from senlin.common import consts
from senlin.engine import event


class TestEvent(testtools.TestCase):

    def setUp(self):
        super(TestEvent, self).setUp()
        logging.register_options(cfg.CONF)

    @mock.patch('stevedore.named.NamedExtensionManager')
    def test_load_dispatcher(self, mock_mgr):

        class FakeDispatcher(object):
            values = {'a': 1, 'b': 2}

            def __iter__(self):
                return iter(self.values)

            def __getitem__(self, key):
                return self.values.get(key, '')

            def __contains__(self, name):
                return name in self.values

            def names(self):
                return self.values.keys()

        mock_mgr.return_value = FakeDispatcher()
        res = event.load_dispatcher()

        self.assertIsNone(res)
        mock_mgr.assert_called_once_with(
            namespace='senlin.dispatchers',
            names=cfg.CONF.event_dispatchers,
            invoke_on_load=True,
            propagate_map_exceptions=True)

    def test_event_data(self):
        entity = mock.Mock(id='ENTITY_ID')
        entity.name = 'FAKE_ENTITY'
        action = mock.Mock(id='ACTION_ID', action='ACTION', entity=entity)

        res = event._event_data(action)

        self.assertEqual({'name': 'FAKE_ENTITY', 'obj_id': 'ENTITY_I',
                          'action': 'ACTION', 'phase': None, 'reason': None,
                          'id': 'ACTION_I'},
                         res)

    def test_event_data_with_phase_reason(self):
        entity = mock.Mock(id='ENTITY_ID')
        entity.name = 'FAKE_ENTITY'
        action = mock.Mock(id='ACTION_ID', action='ACTION', entity=entity)

        res = event._event_data(action, phase='PHASE1', reason='REASON1')

        self.assertEqual({'name': 'FAKE_ENTITY', 'id': 'ACTION_I',
                          'action': 'ACTION', 'phase': 'PHASE1',
                          'obj_id': 'ENTITY_I', 'reason': 'REASON1'},
                         res)

    def test_dump(self):
        cfg.CONF.set_override('debug', True)
        saved_dispathers = event.dispatchers
        event.dispatchers = mock.Mock()
        action = mock.Mock(cause=consts.CAUSE_RPC)
        try:
            event._dump(logging.INFO, action, 'Phase1', 'Reason1', 'TS1')
            event.dispatchers.map_method.assert_called_once_with(
                'dump', logging.INFO, action,
                phase='Phase1', reason='Reason1', timestamp='TS1')
        finally:
            event.dispatchers = saved_dispathers

    def test_dump_without_timestamp(self):
        cfg.CONF.set_override('debug', True)
        saved_dispathers = event.dispatchers
        event.dispatchers = mock.Mock()
        action = mock.Mock(cause=consts.CAUSE_RPC)
        try:
            event._dump(logging.INFO, action, 'Phase1', 'Reason1', None)

            event.dispatchers.map_method.assert_called_once_with(
                'dump', logging.INFO, action,
                phase='Phase1', reason='Reason1', timestamp=mock.ANY)
        finally:
            event.dispatchers = saved_dispathers

    def test_dump_guarded(self):
        cfg.CONF.set_override('debug', False)
        cfg.CONF.set_override('priority', 'warning', group='dispatchers')
        saved_dispathers = event.dispatchers
        event.dispatchers = mock.Mock()
        action = mock.Mock(cause=consts.CAUSE_RPC)
        try:
            event._dump(logging.INFO, action, 'Phase1', 'Reason1', 'TS1')
            # (temporary)Remove map_method.call_count for coverage test
            # self.assertEqual(0, event.dispatchers.map_method.call_count)
        finally:
            event.dispatchers = saved_dispathers

    def test_dump_exclude_derived_actions_positive(self):
        cfg.CONF.set_override('exclude_derived_actions', True,
                              group='dispatchers')
        saved_dispathers = event.dispatchers
        event.dispatchers = mock.Mock()
        action = mock.Mock(cause=consts.CAUSE_DERIVED)
        try:
            event._dump(logging.INFO, action, 'Phase1', 'Reason1', 'TS1')

            self.assertEqual(0, event.dispatchers.map_method.call_count)
        finally:
            event.dispatchers = saved_dispathers

    def test_dump_exclude_derived_actions_negative(self):
        cfg.CONF.set_override('exclude_derived_actions', False,
                              group='dispatchers')
        saved_dispathers = event.dispatchers
        event.dispatchers = mock.Mock()
        action = mock.Mock(cause=consts.CAUSE_DERIVED)
        try:
            event._dump(logging.INFO, action, 'Phase1', 'Reason1', 'TS1')

            event.dispatchers.map_method.assert_called_once_with(
                'dump', logging.INFO, action,
                phase='Phase1', reason='Reason1', timestamp='TS1')
        finally:
            event.dispatchers = saved_dispathers

    def test_dump_with_exception(self):
        cfg.CONF.set_override('debug', True)
        saved_dispathers = event.dispatchers
        event.dispatchers = mock.Mock()
        event.dispatchers.map_method.side_effect = Exception('fab')
        action = mock.Mock(cause=consts.CAUSE_RPC)
        try:
            res = event._dump(logging.INFO, action, 'Phase1', 'Reason1', 'TS1')

            self.assertIsNone(res)  # exception logged only
            event.dispatchers.map_method.assert_called_once_with(
                'dump', logging.INFO, action,
                phase='Phase1', reason='Reason1', timestamp='TS1')
        finally:
            event.dispatchers = saved_dispathers


@mock.patch.object(event, '_dump')
class TestLogMethods(testtools.TestCase):

    def test_critical(self, mock_dump):
        entity = mock.Mock(id='1234567890')
        entity.name = 'fake_obj'
        action = mock.Mock(id='FAKE_ID', entity=entity, action='ACTION_NAME')

        res = event.critical(action, 'P1', 'R1', 'TS1')

        self.assertIsNone(res)
        mock_dump.assert_called_once_with(logging.CRITICAL, action,
                                          'P1', 'R1', 'TS1')

    def test_error(self, mock_dump):
        entity = mock.Mock(id='1234567890')
        entity.name = 'fake_obj'
        action = mock.Mock(id='FAKE_ID', entity=entity, action='ACTION_NAME')

        res = event.error(action, 'P1', 'R1', 'TS1')

        self.assertIsNone(res)
        mock_dump.assert_called_once_with(logging.ERROR, action,
                                          'P1', 'R1', 'TS1')

    def test_warning(self, mock_dump):
        entity = mock.Mock(id='1234567890')
        entity.name = 'fake_obj'
        action = mock.Mock(id='FAKE_ID', entity=entity, action='ACTION_NAME')

        res = event.warning(action, 'P1', 'R1', 'TS1')

        self.assertIsNone(res)
        mock_dump.assert_called_once_with(logging.WARNING, action,
                                          'P1', 'R1', 'TS1')

    def test_info(self, mock_dump):
        entity = mock.Mock(id='1234567890')
        entity.name = 'fake_obj'
        action = mock.Mock(id='FAKE_ID', entity=entity, action='ACTION_NAME')

        res = event.info(action, 'P1', 'R1', 'TS1')

        self.assertIsNone(res)
        mock_dump.assert_called_once_with(logging.INFO, action,
                                          'P1', 'R1', 'TS1')

    def test_debug(self, mock_dump):
        entity = mock.Mock(id='1234567890')
        entity.name = 'fake_obj'
        action = mock.Mock(id='FAKE_ID', entity=entity, action='ACTION_NAME')

        res = event.debug(action, 'P1', 'R1', 'TS1')

        self.assertIsNone(res)
        mock_dump.assert_called_once_with(logging.DEBUG, action,
                                          'P1', 'R1', 'TS1')
