# 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 oslotest import base
import psutil

from monasca_setup.detection.plugins import ceph


MON_PROCESSES = [
    {'name': 'ceph-mon.mon0',
     'type': 'ceph-mon',
     'search_string': [
         '/usr/bin/ceph-mon --cluster ceph --id mon0 -f',
         '/usr/bin/ceph-mon --cluster ceph -f --id mon0',
         '/usr/bin/ceph-mon --id mon0 --cluster ceph -f',
         '/usr/bin/ceph-mon --id mon0 -f --cluster ceph',
         '/usr/bin/ceph-mon -f --cluster ceph --id mon0',
         '/usr/bin/ceph-mon -f --id mon0 --cluster ceph'
     ]},
    {'name': 'ceph1-mon.mon0',
     'type': 'ceph-mon',
     'search_string': [
         '/usr/bin/ceph-mon --cluster ceph1 --id mon0 -f',
         '/usr/bin/ceph-mon --cluster ceph1 -f --id mon0',
         '/usr/bin/ceph-mon --id mon0 --cluster ceph1 -f',
         '/usr/bin/ceph-mon --id mon0 -f --cluster ceph1',
         '/usr/bin/ceph-mon -f --cluster ceph1 --id mon0',
         '/usr/bin/ceph-mon -f --id mon0 --cluster ceph1'
     ]},
]

RGW_PROCESSES = [
    {'name': 'ceph-radosgw.rgw0',
     'type': 'ceph-radosgw',
     'search_string': [
         '/usr/bin/radosgw --cluster ceph --name client.rgw.rgw0 -f',
         '/usr/bin/radosgw --cluster ceph -f --name client.rgw.rgw0',
         '/usr/bin/radosgw --name client.rgw.rgw0 --cluster ceph -f',
         '/usr/bin/radosgw --name client.rgw.rgw0 -f --cluster ceph',
         '/usr/bin/radosgw -f --cluster ceph --name client.rgw.rgw0',
         '/usr/bin/radosgw -f --name client.rgw.rgw0 --cluster ceph'
     ]},
    {'name': 'ceph1-radosgw.rgw0',
     'type': 'ceph-radosgw',
     'search_string': [
         '/usr/bin/radosgw --cluster ceph1 --name client.rgw.rgw0 -f',
         '/usr/bin/radosgw --cluster ceph1 -f --name client.rgw.rgw0',
         '/usr/bin/radosgw --name client.rgw.rgw0 --cluster ceph1 -f',
         '/usr/bin/radosgw --name client.rgw.rgw0 -f --cluster ceph1',
         '/usr/bin/radosgw -f --cluster ceph1 --name client.rgw.rgw0',
         '/usr/bin/radosgw -f --name client.rgw.rgw0 --cluster ceph1'
     ]},
]


def mocked_service_config(*args, **kwargs):
    if args[1] == 'mon':
        return MON_PROCESSES
    elif args[1] == 'radosgw':
        return RGW_PROCESSES
    return []


class FakeProcess(object):
    cmdLine = None

    def as_dict(self, attrs=None):
        all_attrs = {'name': 'ceph',
                     'exe': FakeProcess.exe(),
                     'cmdline': FakeProcess.cmdline()}
        if attrs:
            for key in attrs:
                if key not in all_attrs:
                    all_attrs.pop(key, None)
        return all_attrs

    @staticmethod
    def exe():
        line = FakeProcess.cmdLine
        if not line:
            return None
        return line[0]

    @staticmethod
    def cmdline():
        return FakeProcess.cmdLine


class TestCephDetection(base.BaseTestCase):
    CLUSTERS = [
        {
            'cluster_name': 'ceph',
            'config_file': '/etc/ceph/ceph.conf'
        },
        {
            'cluster_name': 'ceph1',
            'config_file': '/etc/ceph/ceph1.conf'
        },
    ]

    def setUp(self):
        super(TestCephDetection, self).setUp()
        with mock.patch.object(ceph.Ceph, '_detect') as mock_detect:
            self._ceph = ceph.Ceph('ceph')
            self.assertTrue(mock_detect.called)

    def test_should_not_configure_if_no_process(self):
        FakeProcess.cmdLine = []
        self._detect(proc=True)
        self.assertFalse(self._ceph.available)

    def test_should_be_available_if_everything_matches(self):
        ceph_cmd = '/usr/bin/ceph-mon -f --cluster ceph --id mon0 --setuser' \
               ' ceph --setgroup ceph'
        FakeProcess.cmdLine = [ceph_cmd]
        self._detect()
        self.assertTrue(self._ceph.available)

    def test_build_search_string(self):
        executable = '/usr/bin/ceph-mon'
        args = ['--cluster ceph', '--id mon0', '-f']

        expected_strings = [
            '/usr/bin/ceph-mon --cluster ceph --id mon0 -f',
            '/usr/bin/ceph-mon --cluster ceph -f --id mon0',
            '/usr/bin/ceph-mon --id mon0 --cluster ceph -f',
            '/usr/bin/ceph-mon --id mon0 -f --cluster ceph',
            '/usr/bin/ceph-mon -f --cluster ceph --id mon0',
            '/usr/bin/ceph-mon -f --id mon0 --cluster ceph'
        ]

        search_strings = self._ceph._build_search_string(executable, args)
        self.assertEqual(expected_strings, search_strings)

    @mock.patch('os.path.exists', return_value=True)
    @mock.patch('os.listdir', return_value=['ceph-mon0', 'ceph1-mon0'])
    def test_service_config(self, list_dir, path_exists):
        processes = self._ceph._service_config(self.CLUSTERS, 'mon')
        self.assertEqual(MON_PROCESSES, processes)

    @mock.patch('os.path.exists', return_value=True)
    @mock.patch('os.listdir', return_value=['ceph-rgw.rgw0', 'ceph1-rgw.rgw0'])
    def test_radosgw_service_config(self, list_dir, path_exists):
        processes = self._ceph._service_config(self.CLUSTERS, 'radosgw')
        self.assertEqual(RGW_PROCESSES, processes)

    @mock.patch('os.path.exists', return_value=True)
    @mock.patch('os.listdir', return_value=[])
    def test_build_config_with_no_ceph_conf(self, list_dir, path_exists):
        config = self._ceph.build_config()
        self.assertEqual({}, dict(config))

    @mock.patch('os.path.exists', return_value=True)
    @mock.patch('os.listdir', return_value=['ceph.conf', 'ceph1.conf'])
    def test_build_config_with_no_admin_key(self, list_dir, path_exists):
        self._ceph._service_config = mock.Mock(
            side_effect=mocked_service_config)

        processes = MON_PROCESSES + RGW_PROCESSES
        process_instances = list()

        for p in processes:
            instance = {
                'exact_match': False,
                'search_string': p['search_string'],
                'detailed': True,
                'name': p['name'],
                'dimensions':  {'component': p['type'], 'service': 'ceph'}
            }
            process_instances.append(instance)

        expected_config = {
            'process': {
                'init_config': None,
                'instances': process_instances,
            },
            'ceph': {
                'init_config': None,
                'instances': [{'cluster_name': 'ceph',
                               'collect_mon_metrics': False,
                               'collect_osd_metrics': False,
                               'collect_pool_metrics': False,
                               'collect_stats_metrics': False,
                               'collect_usage_metrics': False},
                              {'cluster_name': 'ceph1',
                               'collect_mon_metrics': False,
                               'collect_osd_metrics': False,
                               'collect_pool_metrics': False,
                               'collect_stats_metrics': False,
                               'collect_usage_metrics': False}]
            }
        }
        config = self._ceph.build_config()
        self.assertEqual(expected_config, dict(config))

    @mock.patch('os.path.exists', return_value=True)
    @mock.patch('os.listdir', return_value=['ceph.conf', 'ceph1.conf', 'ceph1.client.admin.keyring'])
    def test_build_config(self, list_dir, path_exists):
        self._ceph._service_config = mock.Mock(
            side_effect=mocked_service_config)

        processes = MON_PROCESSES + RGW_PROCESSES
        process_instances = list()

        for p in processes:
            instance = {
                'exact_match': False,
                'search_string': p['search_string'],
                'detailed': True,
                'name': p['name'],
                'dimensions':  {'component': p['type'], 'service': 'ceph'}
            }
            process_instances.append(instance)

        expected_config = {
            'process': {
                'init_config': None,
                'instances': process_instances,
            },
            'ceph': {
                'init_config': None,
                'instances': [{'cluster_name': 'ceph',
                               'collect_mon_metrics': False,
                               'collect_osd_metrics': False,
                               'collect_pool_metrics': False,
                               'collect_stats_metrics': False,
                               'collect_usage_metrics': False},
                              {'cluster_name': 'ceph1'}]
            }
        }
        config = self._ceph.build_config()
        self.assertEqual(expected_config, dict(config))

    def _detect(self, proc=False):
        self._ceph.available = False
        processes = [FakeProcess()] if not proc else []
        process_iter = mock.patch.object(psutil, 'process_iter',
                                         return_value=processes)
        with process_iter as mock_process_iter:
            self._ceph._detect()
            self.assertTrue(mock_process_iter.called)
