import os
from mock import MagicMock, patch, call

os.environ['JUJU_UNIT_NAME'] = 'cinder'
import cinder_utils as utils

# Need to do some early patching to get the module loaded.
# _restart_map = utils.restart_map
_register_configs = utils.register_configs
_service_enabled = utils.service_enabled
utils.register_configs = MagicMock()
utils.service_enabled = MagicMock()

import cinder_hooks as hooks
hooks.hooks._config_save = False

# Unpatch it now that its loaded.
utils.register_configs = _register_configs
utils.service_enabled = _service_enabled

from test_utils import (
    CharmTestCase,
    RESTART_MAP,
)

TO_PATCH = [
    # cinder_utils
    'determine_packages',
    'juju_log',
    'lsb_release',
    'migrate_database',
    'configure_lvm_storage',
    'register_configs',
    'service_enabled',
    'set_ceph_env_variables',
    'CONFIGS',
    'CLUSTER_RES',
    # charmhelpers.core.hookenv
    'config',
    'relation_set',
    'relation_get',
    'relation_ids',
    'service_name',
    'unit_get',
    # charmhelpers.core.host
    'apt_install',
    'apt_update',
    # charmhelpers.contrib.openstack.openstack_utils
    'configure_installation_source',
    # charmhelpers.contrib.hahelpers.cluster_utils
    'eligible_leader',
    'get_hacluster_config',
    # charmhelpers.contrib.network.ip
    'get_iface_for_address',
    'get_netmask_for_address',
    'get_address_in_network',
]


class TestClusterHooks(CharmTestCase):

    def setUp(self):
        super(TestClusterHooks, self).setUp(hooks, TO_PATCH)
        self.config.side_effect = self.test_config.get

    @patch('charmhelpers.core.host.service')
    @patch('charmhelpers.core.host.file_hash')
    def test_cluster_hook(self, file_hash, service):
        'Ensure API restart before haproxy on cluster changed'
        # set first hash lookup on all files
        side_effects = []
        # set first hash lookup on all configs in restart_on_change
        [side_effects.append('foo') for f in RESTART_MAP.keys()]
        # set second hash lookup on all configs in restart_on_change
        [side_effects.append('bar') for f in RESTART_MAP.keys()]
        file_hash.side_effect = side_effects
        hooks.hooks.execute(['hooks/cluster-relation-changed'])
        ex = [
            call('stop', 'cinder-api'),
            call('stop', 'cinder-volume'),
            call('stop', 'cinder-scheduler'),
            call('stop', 'haproxy'),
            call('stop', 'apache2'),
            call('start', 'cinder-api'),
            call('start', 'cinder-volume'),
            call('start', 'cinder-scheduler'),
            call('start', 'haproxy'),
            call('start', 'apache2')]
        self.assertEquals(ex, service.call_args_list)

    def test_cluster_joined_hook(self):
        self.config.side_effect = self.test_config.get
        self.get_address_in_network.return_value = None
        hooks.hooks.execute(['hooks/cluster-relation-joined'])
        self.assertFalse(self.relation_set.called)

    def test_cluster_joined_hook_multinet(self):
        self.config.side_effect = self.test_config.get
        self.get_address_in_network.side_effect = [
            '192.168.20.2',
            '10.20.3.2',
            '146.162.23.45'
        ]
        hooks.hooks.execute(['hooks/cluster-relation-joined'])
        self.relation_set.assert_has_calls([
            call(relation_id=None,
                 relation_settings={'admin-address': '192.168.20.2'}),
            call(relation_id=None,
                 relation_settings={'internal-address': '10.20.3.2'}),
            call(relation_id=None,
                 relation_settings={'public-address': '146.162.23.45'}),
        ])

    def test_ha_joined_complete_config(self):
        'Ensure hacluster subordinate receives all relevant config'
        conf = {
            'ha-bindiface': 'eth100',
            'ha-mcastport': '37373',
            'vip': '192.168.25.163',
            'vip_iface': 'eth101',
            'vip_cidr': '19',
        }

        self.test_config.set('prefer-ipv6', 'False')
        self.get_hacluster_config.return_value = conf
        self.get_iface_for_address.return_value = 'eth101'
        self.get_netmask_for_address.return_value = '255.255.224.0'
        hooks.hooks.execute(['hooks/ha-relation-joined'])
        ex_args = {
            'corosync_mcastport': '37373',
            'init_services': {'res_cinder_haproxy': 'haproxy'},
            'resource_params': {
                'res_cinder_eth101_vip':
                'params ip="192.168.25.163" cidr_netmask="255.255.224.0"'
                ' nic="eth101"',
                'res_cinder_haproxy': 'op monitor interval="5s"'
            },
            'corosync_bindiface': 'eth100',
            'relation_id': None,
            'clones': {'cl_cinder_haproxy': 'res_cinder_haproxy'},
            'resources': {
                'res_cinder_eth101_vip': 'ocf:heartbeat:IPaddr2',
                'res_cinder_haproxy': 'lsb:haproxy'
            }
        }
        self.relation_set.assert_has_calls([
            call(relation_id=None,
                 groups={'grp_cinder_vips': 'res_cinder_eth101_vip'}),
            call(**ex_args)
        ])

    def test_ha_joined_complete_config_with_ipv6(self):
        'Ensure hacluster subordinate receives all relevant config'
        conf = {
            'ha-bindiface': 'eth100',
            'ha-mcastport': '37373',
            'vip': '2001:db8:1::1',
            'vip_iface': 'eth101',
            'vip_cidr': '64',
        }

        self.test_config.set('prefer-ipv6', 'True')
        self.get_hacluster_config.return_value = conf
        self.get_iface_for_address.return_value = 'eth101'
        self.get_netmask_for_address.return_value = 'ffff.ffff.ffff.ffff'
        hooks.hooks.execute(['hooks/ha-relation-joined'])
        ex_args = {
            'relation_id': None,
            'corosync_mcastport': '37373',
            'init_services': {'res_cinder_haproxy': 'haproxy'},
            'resource_params': {
                'res_cinder_eth101_vip':
                'params ipv6addr="2001:db8:1::1" '
                'cidr_netmask="ffff.ffff.ffff.ffff" '
                'nic="eth101"',
                'res_cinder_haproxy': 'op monitor interval="5s"'
            },
            'corosync_bindiface': 'eth100',
            'clones': {'cl_cinder_haproxy': 'res_cinder_haproxy'},
            'resources': {
                'res_cinder_eth101_vip': 'ocf:heartbeat:IPv6addr',
                'res_cinder_haproxy': 'lsb:haproxy'
            }
        }
        self.relation_set.assert_called_with(**ex_args)

    def test_ha_joined_no_bound_ip(self):
        '''
        Ensure fallback configuration options are used if network
        interface cannot be auto-detected
        '''
        conf = {
            'ha-bindiface': 'eth100',
            'ha-mcastport': '37373',
            'vip': '192.168.25.163',
        }

        self.test_config.set('prefer-ipv6', 'False')
        self.test_config.set('vip_iface', 'eth120')
        self.test_config.set('vip_cidr', '21')
        self.get_hacluster_config.return_value = conf
        self.get_iface_for_address.return_value = None
        self.get_netmask_for_address.return_value = None
        hooks.hooks.execute(['hooks/ha-relation-joined'])
        ex_args = {
            'relation_id': None,
            'corosync_mcastport': '37373',
            'init_services': {'res_cinder_haproxy': 'haproxy'},
            'resource_params': {
                'res_cinder_eth120_vip':
                'params ip="192.168.25.163" cidr_netmask="21"'
                ' nic="eth120"',
                'res_cinder_haproxy': 'op monitor interval="5s"'
            },
            'corosync_bindiface': 'eth100',
            'clones': {'cl_cinder_haproxy': 'res_cinder_haproxy'},
            'resources': {
                'res_cinder_eth120_vip': 'ocf:heartbeat:IPaddr2',
                'res_cinder_haproxy': 'lsb:haproxy'
            }
        }
        self.relation_set.assert_has_calls([
            call(relation_id=None,
                 groups={'grp_cinder_vips': 'res_cinder_eth120_vip'}),
            call(**ex_args)
        ])

    @patch.object(hooks, 'identity_joined')
    def test_ha_changed_clustered(self, joined):
        self.relation_get.return_value = True
        self.relation_ids.return_value = ['identity:0']
        hooks.hooks.execute(['hooks/ha-relation-changed'])
        joined.assert_called_with(rid='identity:0')

    def test_ha_changed_not_clustered(self):
        'Ensure ha_changed exits early if not yet clustered'
        self.relation_get.return_value = None
        hooks.hooks.execute(['hooks/ha-relation-changed'])
        self.assertTrue(self.juju_log.called)