import json
import mock
import unittest

import charmhelpers.contrib.openstack.cert_utils as cert_utils


class CertUtilsTests(unittest.TestCase):

    def test_CertRequest(self):
        cr = cert_utils.CertRequest()
        self.assertEqual(cr.entries, [])
        self.assertIsNone(cr.hostname_entry)

    @mock.patch.object(cert_utils, 'local_unit', return_value='unit/2')
    def test_CertRequest_add_entry(self, local_unit):
        cr = cert_utils.CertRequest()
        cr.add_entry('admin', 'admin.openstack.local', ['10.10.10.10'])
        self.assertEqual(
            cr.get_request(),
            {'cert_requests':
                '{"admin.openstack.local": {"sans": ["10.10.10.10"]}}',
             'unit_name': 'unit_2'})

    @mock.patch.object(cert_utils, 'local_unit', return_value='unit/2')
    @mock.patch.object(cert_utils, 'resolve_network_cidr')
    @mock.patch.object(cert_utils, 'get_vip_in_network')
    @mock.patch.object(cert_utils, 'get_hostname')
    @mock.patch.object(cert_utils, 'local_address')
    def test_CertRequest_add_hostname_cn(self, local_address, get_hostname,
                                         get_vip_in_network,
                                         resolve_network_cidr, local_unit):
        resolve_network_cidr.side_effect = lambda x: x
        get_vip_in_network.return_value = '10.1.2.100'
        local_address.return_value = '10.1.2.3'
        get_hostname.return_value = 'juju-unit-2'
        cr = cert_utils.CertRequest()
        cr.add_hostname_cn()
        self.assertEqual(
            cr.get_request(),
            {'cert_requests':
                '{"juju-unit-2": {"sans": ["10.1.2.100", "10.1.2.3"]}}',
             'unit_name': 'unit_2'})

    @mock.patch.object(cert_utils, 'local_unit', return_value='unit/2')
    @mock.patch.object(cert_utils, 'resolve_network_cidr')
    @mock.patch.object(cert_utils, 'get_vip_in_network')
    @mock.patch.object(cert_utils, 'get_hostname')
    @mock.patch.object(cert_utils, 'local_address')
    def test_CertRequest_add_hostname_cn_ip(self, local_address, get_hostname,
                                            get_vip_in_network,
                                            resolve_network_cidr, local_unit):
        resolve_network_cidr.side_effect = lambda x: x
        get_vip_in_network.return_value = '10.1.2.100'
        local_address.return_value = '10.1.2.3'
        get_hostname.return_value = 'juju-unit-2'
        cr = cert_utils.CertRequest()
        cr.add_hostname_cn()
        cr.add_hostname_cn_ip(['10.1.2.4'])
        self.assertEqual(
            cr.get_request(),
            {'cert_requests':
                ('{"juju-unit-2": {"sans": ["10.1.2.100", "10.1.2.3", '
                 '"10.1.2.4"]}}'),
             'unit_name': 'unit_2'})

    @mock.patch.object(cert_utils, 'get_certificate_sans')
    @mock.patch.object(cert_utils, 'local_unit', return_value='unit/2')
    @mock.patch.object(cert_utils, 'resolve_network_cidr')
    @mock.patch.object(cert_utils, 'get_vip_in_network')
    @mock.patch.object(cert_utils, 'network_get_primary_address')
    @mock.patch.object(cert_utils, 'resolve_address')
    @mock.patch.object(cert_utils, 'config')
    @mock.patch.object(cert_utils, 'get_hostname')
    @mock.patch.object(cert_utils, 'local_address')
    def test_get_certificate_request(self, local_address, get_hostname,
                                     config, resolve_address,
                                     network_get_primary_address,
                                     get_vip_in_network, resolve_network_cidr,
                                     local_unit, get_certificate_sans):
        local_address.return_value = '10.1.2.3'
        get_hostname.return_value = 'juju-unit-2'
        _config = {
            'os-internal-hostname': 'internal.openstack.local',
            'os-admin-hostname': 'admin.openstack.local',
            'os-public-hostname': 'public.openstack.local',
        }
        _resolve_address = {
            'int': '10.0.0.2',
            'internal': '10.0.0.2',
            'admin': '10.10.0.2',
            'public': '10.20.0.2',
        }
        _npa = {
            'internal': '10.0.0.3',
            'admin': '10.10.0.3',
            'public': '10.20.0.3',
        }
        _vips = {
            '10.0.0.0/16': '10.0.0.100',
            '10.10.0.0/16': '10.10.0.100',
            '10.20.0.0/16': '10.20.0.100',
        }
        _resolve_nets = {
            '10.0.0.3': '10.0.0.0/16',
            '10.10.0.3': '10.10.0.0/16',
            '10.20.0.3': '10.20.0.0/16',
        }
        get_certificate_sans.return_value = list(set(
            list(_resolve_address.values()) +
            list(_npa.values()) +
            list(_vips.values())))
        expect = {
            'admin.openstack.local': {
                'sans': ['10.10.0.100', '10.10.0.2', '10.10.0.3']},
            'internal.openstack.local': {
                'sans': ['10.0.0.100', '10.0.0.2', '10.0.0.3']},
            'juju-unit-2': {'sans': ['10.1.2.3']},
            'public.openstack.local': {
                'sans': ['10.20.0.100', '10.20.0.2', '10.20.0.3']}}
        self.maxDiff = None
        config.side_effect = lambda x: _config.get(x)
        get_vip_in_network.side_effect = lambda x: _vips.get(x)
        resolve_network_cidr.side_effect = lambda x: _resolve_nets.get(x)
        network_get_primary_address.side_effect = lambda x: _npa.get(x)
        resolve_address.side_effect = \
            lambda endpoint_type: _resolve_address[endpoint_type]
        output = json.loads(
            cert_utils.get_certificate_request()['cert_requests'])
        self.assertEqual(
            output,
            expect)
        get_certificate_sans.assert_called_once_with(
            bindings=['internal', 'admin', 'public'])

    @mock.patch.object(cert_utils, 'get_certificate_sans')
    @mock.patch.object(cert_utils, 'local_unit', return_value='unit/2')
    @mock.patch.object(cert_utils, 'resolve_network_cidr')
    @mock.patch.object(cert_utils, 'get_vip_in_network')
    @mock.patch.object(cert_utils, 'network_get_primary_address')
    @mock.patch.object(cert_utils, 'resolve_address')
    @mock.patch.object(cert_utils, 'config')
    @mock.patch.object(cert_utils, 'get_hostname')
    @mock.patch.object(cert_utils, 'local_address')
    def test_get_certificate_request_no_hostnames(
            self, local_address, get_hostname, config, resolve_address,
            network_get_primary_address, get_vip_in_network,
            resolve_network_cidr, local_unit, get_certificate_sans):
        local_address.return_value = '10.1.2.3'
        get_hostname.return_value = 'juju-unit-2'
        _config = {
            'os-admin-hostname': 'admin.openstack.local',
            'os-public-hostname': 'public.openstack.local',
        }
        _resolve_address = {
            'int': '10.0.0.2',
            'internal': '10.0.0.2',
            'admin': '10.10.0.2',
            'public': '10.20.0.2',
        }
        _npa = {
            'internal': '10.0.0.3',
            'admin': '10.10.0.3',
            'public': '10.20.0.3',
            'mybinding': '10.30.0.3',
        }
        _vips = {
            '10.0.0.0/16': '10.0.0.100',
            '10.10.0.0/16': '10.10.0.100',
            '10.20.0.0/16': '10.20.0.100',
        }
        _resolve_nets = {
            '10.0.0.3': '10.0.0.0/16',
            '10.10.0.3': '10.10.0.0/16',
            '10.20.0.3': '10.20.0.0/16',
        }
        get_certificate_sans.return_value = list(set(
            list(_resolve_address.values()) +
            list(_npa.values()) +
            list(_vips.values())))
        expect = {
            'admin.openstack.local': {
                'sans': ['10.10.0.100', '10.10.0.2', '10.10.0.3']},
            'juju-unit-2': {'sans': [
                '10.0.0.100', '10.0.0.2', '10.0.0.3', '10.1.2.3', '10.30.0.3']},
            'public.openstack.local': {
                'sans': ['10.20.0.100', '10.20.0.2', '10.20.0.3']}}
        self.maxDiff = None
        config.side_effect = lambda x: _config.get(x)
        get_vip_in_network.side_effect = lambda x: _vips.get(x)
        resolve_network_cidr.side_effect = lambda x: _resolve_nets.get(x)
        network_get_primary_address.side_effect = lambda x: _npa.get(x)
        resolve_address.side_effect = \
            lambda endpoint_type: _resolve_address[endpoint_type]
        output = json.loads(
            cert_utils.get_certificate_request(
                bindings=['mybinding'])['cert_requests'])
        self.assertEqual(
            output,
            expect)
        get_certificate_sans.assert_called_once_with(
            bindings=['mybinding', 'internal', 'admin', 'public'])

    @mock.patch.object(cert_utils, 'get_certificate_request')
    @mock.patch.object(cert_utils, 'local_address')
    @mock.patch.object(cert_utils.os, 'symlink')
    @mock.patch.object(cert_utils.os.path, 'isfile')
    @mock.patch.object(cert_utils, 'get_hostname')
    def test_create_ip_cert_links(self, get_hostname, isfile,
                                  symlink, local_address, get_cert_request):
        cert_request = {'cert_requests': {
            'admin.openstack.local': {
                'sans': ['10.10.0.100', '10.10.0.2', '10.10.0.3']},
            'internal.openstack.local': {
                'sans': ['10.0.0.100', '10.0.0.2', '10.0.0.3']},
            'juju-unit-2': {'sans': ['10.1.2.3']},
            'public.openstack.local': {
                'sans': ['10.20.0.100', '10.20.0.2', '10.20.0.3']}}}
        get_cert_request.return_value = cert_request
        _files = {
            '/etc/ssl/cert_juju-unit-2': True,
            '/etc/ssl/cert_10.1.2.3': False,
            '/etc/ssl/cert_admin.openstack.local': True,
            '/etc/ssl/cert_10.10.0.100': False,
            '/etc/ssl/cert_10.10.0.2': False,
            '/etc/ssl/cert_10.10.0.3': False,
            '/etc/ssl/cert_internal.openstack.local': True,
            '/etc/ssl/cert_10.0.0.100': False,
            '/etc/ssl/cert_10.0.0.2': False,
            '/etc/ssl/cert_10.0.0.3': False,
            '/etc/ssl/cert_public.openstack.local': True,
            '/etc/ssl/cert_10.20.0.100': False,
            '/etc/ssl/cert_10.20.0.2': False,
            '/etc/ssl/cert_10.20.0.3': False,
            '/etc/ssl/cert_funky-name': False,
        }
        isfile.side_effect = lambda x: _files[x]
        expected = [
            mock.call('/etc/ssl/cert_admin.openstack.local', '/etc/ssl/cert_10.10.0.100'),
            mock.call('/etc/ssl/key_admin.openstack.local', '/etc/ssl/key_10.10.0.100'),
            mock.call('/etc/ssl/cert_admin.openstack.local', '/etc/ssl/cert_10.10.0.2'),
            mock.call('/etc/ssl/key_admin.openstack.local', '/etc/ssl/key_10.10.0.2'),
            mock.call('/etc/ssl/cert_admin.openstack.local', '/etc/ssl/cert_10.10.0.3'),
            mock.call('/etc/ssl/key_admin.openstack.local', '/etc/ssl/key_10.10.0.3'),
            mock.call('/etc/ssl/cert_internal.openstack.local', '/etc/ssl/cert_10.0.0.100'),
            mock.call('/etc/ssl/key_internal.openstack.local', '/etc/ssl/key_10.0.0.100'),
            mock.call('/etc/ssl/cert_internal.openstack.local', '/etc/ssl/cert_10.0.0.2'),
            mock.call('/etc/ssl/key_internal.openstack.local', '/etc/ssl/key_10.0.0.2'),
            mock.call('/etc/ssl/cert_internal.openstack.local', '/etc/ssl/cert_10.0.0.3'),
            mock.call('/etc/ssl/key_internal.openstack.local', '/etc/ssl/key_10.0.0.3'),
            mock.call('/etc/ssl/cert_juju-unit-2', '/etc/ssl/cert_10.1.2.3'),
            mock.call('/etc/ssl/key_juju-unit-2', '/etc/ssl/key_10.1.2.3'),
            mock.call('/etc/ssl/cert_public.openstack.local', '/etc/ssl/cert_10.20.0.100'),
            mock.call('/etc/ssl/key_public.openstack.local', '/etc/ssl/key_10.20.0.100'),
            mock.call('/etc/ssl/cert_public.openstack.local', '/etc/ssl/cert_10.20.0.2'),
            mock.call('/etc/ssl/key_public.openstack.local', '/etc/ssl/key_10.20.0.2'),
            mock.call('/etc/ssl/cert_public.openstack.local', '/etc/ssl/cert_10.20.0.3'),
            mock.call('/etc/ssl/key_public.openstack.local', '/etc/ssl/key_10.20.0.3')]
        cert_utils.create_ip_cert_links('/etc/ssl')
        symlink.assert_has_calls(expected, any_order=True)
        # Customer hostname
        symlink.reset_mock()
        get_hostname.return_value = 'juju-unit-2'
        cert_utils.create_ip_cert_links(
            '/etc/ssl',
            custom_hostname_link='funky-name')
        expected.extend([
            mock.call('/etc/ssl/cert_juju-unit-2', '/etc/ssl/cert_funky-name'),
            mock.call('/etc/ssl/key_juju-unit-2', '/etc/ssl/key_funky-name'),
        ])
        symlink.assert_has_calls(expected, any_order=True)
        get_cert_request.assert_called_with(
            json_encode=False, bindings=['internal', 'admin', 'public'])

    @mock.patch.object(cert_utils, 'get_certificate_request')
    @mock.patch.object(cert_utils, 'local_address')
    @mock.patch.object(cert_utils.os, 'symlink')
    @mock.patch.object(cert_utils.os.path, 'isfile')
    @mock.patch.object(cert_utils, 'get_hostname')
    def test_create_ip_cert_links_bindings(
            self, get_hostname, isfile, symlink, local_address, get_cert_request):
        cert_request = {'cert_requests': {
            'admin.openstack.local': {
                'sans': ['10.10.0.100', '10.10.0.2', '10.10.0.3']},
            'internal.openstack.local': {
                'sans': ['10.0.0.100', '10.0.0.2', '10.0.0.3']},
            'juju-unit-2': {'sans': ['10.1.2.3']},
            'public.openstack.local': {
                'sans': ['10.20.0.100', '10.20.0.2', '10.20.0.3']}}}
        get_cert_request.return_value = cert_request
        _files = {
            '/etc/ssl/cert_juju-unit-2': True,
            '/etc/ssl/cert_10.1.2.3': False,
            '/etc/ssl/cert_admin.openstack.local': True,
            '/etc/ssl/cert_10.10.0.100': False,
            '/etc/ssl/cert_10.10.0.2': False,
            '/etc/ssl/cert_10.10.0.3': False,
            '/etc/ssl/cert_internal.openstack.local': True,
            '/etc/ssl/cert_10.0.0.100': False,
            '/etc/ssl/cert_10.0.0.2': False,
            '/etc/ssl/cert_10.0.0.3': False,
            '/etc/ssl/cert_public.openstack.local': True,
            '/etc/ssl/cert_10.20.0.100': False,
            '/etc/ssl/cert_10.20.0.2': False,
            '/etc/ssl/cert_10.20.0.3': False,
            '/etc/ssl/cert_funky-name': False,
        }
        isfile.side_effect = lambda x: _files[x]
        expected = [
            mock.call('/etc/ssl/cert_admin.openstack.local', '/etc/ssl/cert_10.10.0.100'),
            mock.call('/etc/ssl/key_admin.openstack.local', '/etc/ssl/key_10.10.0.100'),
            mock.call('/etc/ssl/cert_admin.openstack.local', '/etc/ssl/cert_10.10.0.2'),
            mock.call('/etc/ssl/key_admin.openstack.local', '/etc/ssl/key_10.10.0.2'),
            mock.call('/etc/ssl/cert_admin.openstack.local', '/etc/ssl/cert_10.10.0.3'),
            mock.call('/etc/ssl/key_admin.openstack.local', '/etc/ssl/key_10.10.0.3'),
            mock.call('/etc/ssl/cert_internal.openstack.local', '/etc/ssl/cert_10.0.0.100'),
            mock.call('/etc/ssl/key_internal.openstack.local', '/etc/ssl/key_10.0.0.100'),
            mock.call('/etc/ssl/cert_internal.openstack.local', '/etc/ssl/cert_10.0.0.2'),
            mock.call('/etc/ssl/key_internal.openstack.local', '/etc/ssl/key_10.0.0.2'),
            mock.call('/etc/ssl/cert_internal.openstack.local', '/etc/ssl/cert_10.0.0.3'),
            mock.call('/etc/ssl/key_internal.openstack.local', '/etc/ssl/key_10.0.0.3'),
            mock.call('/etc/ssl/cert_juju-unit-2', '/etc/ssl/cert_10.1.2.3'),
            mock.call('/etc/ssl/key_juju-unit-2', '/etc/ssl/key_10.1.2.3'),
            mock.call('/etc/ssl/cert_public.openstack.local', '/etc/ssl/cert_10.20.0.100'),
            mock.call('/etc/ssl/key_public.openstack.local', '/etc/ssl/key_10.20.0.100'),
            mock.call('/etc/ssl/cert_public.openstack.local', '/etc/ssl/cert_10.20.0.2'),
            mock.call('/etc/ssl/key_public.openstack.local', '/etc/ssl/key_10.20.0.2'),
            mock.call('/etc/ssl/cert_public.openstack.local', '/etc/ssl/cert_10.20.0.3'),
            mock.call('/etc/ssl/key_public.openstack.local', '/etc/ssl/key_10.20.0.3')]
        cert_utils.create_ip_cert_links('/etc/ssl', bindings=['mybindings'])
        symlink.assert_has_calls(expected, any_order=True)
        # Customer hostname
        symlink.reset_mock()
        get_hostname.return_value = 'juju-unit-2'
        cert_utils.create_ip_cert_links(
            '/etc/ssl',
            custom_hostname_link='funky-name', bindings=['mybinding'])
        expected.extend([
            mock.call('/etc/ssl/cert_juju-unit-2', '/etc/ssl/cert_funky-name'),
            mock.call('/etc/ssl/key_juju-unit-2', '/etc/ssl/key_funky-name'),
        ])
        symlink.assert_has_calls(expected, any_order=True)
        get_cert_request.assert_called_with(
            json_encode=False, bindings=['mybinding', 'internal', 'admin', 'public'])

    @mock.patch.object(cert_utils, 'write_file')
    def test_install_certs(self, write_file):
        certs = {
            'admin.openstack.local': {
                'cert': 'ADMINCERT',
                'key': 'ADMINKEY'}}
        cert_utils.install_certs('/etc/ssl', certs, chain='CHAIN')
        expected = [
            mock.call(
                path='/etc/ssl/cert_admin.openstack.local',
                content='ADMINCERT\nCHAIN',
                owner='root', group='root',
                perms=0o640),
            mock.call(
                path='/etc/ssl/key_admin.openstack.local',
                content='ADMINKEY',
                owner='root', group='root',
                perms=0o640),
        ]
        write_file.assert_has_calls(expected)

    @mock.patch.object(cert_utils, 'write_file')
    def test_install_certs_ca(self, write_file):
        certs = {
            'admin.openstack.local': {
                'cert': 'ADMINCERT',
                'key': 'ADMINKEY'}}
        ca = 'MYCA'
        cert_utils.install_certs('/etc/ssl', certs, ca)
        expected = [
            mock.call(
                path='/etc/ssl/cert_admin.openstack.local',
                content='ADMINCERT\nMYCA',
                owner='root', group='root',
                perms=0o640),
            mock.call(
                path='/etc/ssl/key_admin.openstack.local',
                content='ADMINKEY',
                owner='root', group='root',
                perms=0o640),
        ]
        write_file.assert_has_calls(expected)

    @mock.patch.object(cert_utils, '_manage_ca_certs')
    @mock.patch.object(cert_utils, 'remote_service_name')
    @mock.patch.object(cert_utils, 'local_unit')
    @mock.patch.object(cert_utils, 'create_ip_cert_links')
    @mock.patch.object(cert_utils, 'install_certs')
    @mock.patch.object(cert_utils, 'install_ca_cert')
    @mock.patch.object(cert_utils, 'mkdir')
    @mock.patch.object(cert_utils, 'relation_get')
    def test_process_certificates(self, relation_get, mkdir, install_ca_cert,
                                  install_certs, create_ip_cert_links,
                                  local_unit, remote_service_name,
                                  _manage_ca_certs):
        remote_service_name.return_value = 'vault'
        local_unit.return_value = 'devnull/2'
        certs = {
            'admin.openstack.local': {
                'cert': 'ADMINCERT',
                'key': 'ADMINKEY'}}
        _relation_info = {
            'keystone_2.processed_requests': json.dumps(certs),
            'chain': 'MYCHAIN',
            'ca': 'ROOTCA',
        }
        relation_get.return_value = _relation_info
        self.assertFalse(cert_utils.process_certificates(
            'myservice',
            'certificates:2',
            'vault/0',
            custom_hostname_link='funky-name'))
        local_unit.return_value = 'keystone/2'
        self.assertTrue(cert_utils.process_certificates(
            'myservice',
            'certificates:2',
            'vault/0',
            custom_hostname_link='funky-name'))
        _manage_ca_certs.assert_called_once_with(
            'ROOTCA', 'certificates:2')
        install_certs.assert_called_once_with(
            '/etc/apache2/ssl/myservice',
            {'admin.openstack.local': {
                'key': 'ADMINKEY', 'cert': 'ADMINCERT'}},
            'MYCHAIN', user='root', group='root')
        create_ip_cert_links.assert_called_once_with(
            '/etc/apache2/ssl/myservice',
            custom_hostname_link='funky-name',
            bindings=['internal', 'admin', 'public'])

    @mock.patch.object(cert_utils, '_manage_ca_certs')
    @mock.patch.object(cert_utils, 'remote_service_name')
    @mock.patch.object(cert_utils, 'local_unit')
    @mock.patch.object(cert_utils, 'create_ip_cert_links')
    @mock.patch.object(cert_utils, 'install_certs')
    @mock.patch.object(cert_utils, 'install_ca_cert')
    @mock.patch.object(cert_utils, 'mkdir')
    @mock.patch.object(cert_utils, 'relation_get')
    def test_process_certificates_bindings(
            self, relation_get, mkdir, install_ca_cert,
            install_certs, create_ip_cert_links,
            local_unit, remote_service_name, _manage_ca_certs):
        remote_service_name.return_value = 'vault'
        local_unit.return_value = 'devnull/2'
        certs = {
            'admin.openstack.local': {
                'cert': 'ADMINCERT',
                'key': 'ADMINKEY'}}
        _relation_info = {
            'keystone_2.processed_requests': json.dumps(certs),
            'chain': 'MYCHAIN',
            'ca': 'ROOTCA',
        }
        relation_get.return_value = _relation_info
        self.assertFalse(cert_utils.process_certificates(
            'myservice',
            'certificates:2',
            'vault/0',
            custom_hostname_link='funky-name'))
        local_unit.return_value = 'keystone/2'
        self.assertTrue(cert_utils.process_certificates(
            'myservice',
            'certificates:2',
            'vault/0',
            custom_hostname_link='funky-name',
            bindings=['mybinding']))
        _manage_ca_certs.assert_called_once_with(
            'ROOTCA', 'certificates:2')
        install_certs.assert_called_once_with(
            '/etc/apache2/ssl/myservice',
            {'admin.openstack.local': {
                'key': 'ADMINKEY', 'cert': 'ADMINCERT'}},
            'MYCHAIN', user='root', group='root')
        create_ip_cert_links.assert_called_once_with(
            '/etc/apache2/ssl/myservice',
            custom_hostname_link='funky-name',
            bindings=['mybinding', 'internal', 'admin', 'public'])

    @mock.patch.object(cert_utils, 'remote_service_name')
    @mock.patch.object(cert_utils, 'relation_ids')
    def test_get_cert_relation_ca_name(self, relation_ids, remote_service_name):
        remote_service_name.return_value = 'vault'

        # Test with argument:
        self.assertEqual(cert_utils.get_cert_relation_ca_name('certificates:1'),
                         'vault_juju_ca_cert')
        remote_service_name.assert_called_once_with(relid='certificates:1')
        remote_service_name.reset_mock()

        # Test without argument:
        relation_ids.return_value = ['certificates:2']
        self.assertEqual(cert_utils.get_cert_relation_ca_name(),
                         'vault_juju_ca_cert')
        remote_service_name.assert_called_once_with(relid='certificates:2')
        remote_service_name.reset_mock()

        # Test without argument nor 'certificates' relation:
        relation_ids.return_value = []
        self.assertEqual(cert_utils.get_cert_relation_ca_name(), '')
        remote_service_name.assert_not_called()

    @mock.patch.object(cert_utils, 'log')
    @mock.patch.object(cert_utils, 'remote_service_name')
    @mock.patch.object(cert_utils.os, 'remove')
    @mock.patch.object(cert_utils.os.path, 'exists')
    @mock.patch.object(cert_utils, 'config')
    @mock.patch.object(cert_utils, 'install_ca_cert')
    def test__manage_ca_certs(self, install_ca_cert, config, os_exists,
                              os_remove, remote_service_name, _log):
        remote_service_name.return_value = 'vault'
        _config = {}
        config.side_effect = lambda x: _config.get(x)
        os_exists.return_value = False
        cert_utils._manage_ca_certs('CA', 'certificates:2')
        install_ca_cert.assert_called_once_with(
            b'CA',
            name='vault_juju_ca_cert')
        self.assertFalse(os_remove.called)
        # Test old cert removed.
        install_ca_cert.reset_mock()
        os_exists.reset_mock()
        os_exists.return_value = True
        cert_utils._manage_ca_certs('CA', 'certificates:2')
        install_ca_cert.assert_called_once_with(
            b'CA',
            name='vault_juju_ca_cert')
        os_remove.assert_called_once_with(
            '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt')
        # Test cert is installed from config
        _config['ssl_ca'] = 'Q0FGUk9NQ09ORklHCg=='
        install_ca_cert.reset_mock()
        os_remove.reset_mock()
        os_exists.reset_mock()
        os_exists.return_value = True
        cert_utils._manage_ca_certs('CA', 'certificates:2')
        expected = [
            mock.call(b'CAFROMCONFIG', name='keystone_juju_ca_cert'),
            mock.call(b'CA', name='vault_juju_ca_cert')]
        install_ca_cert.assert_has_calls(expected)
        self.assertFalse(os_remove.called)

    @mock.patch.object(cert_utils, 'local_unit')
    @mock.patch.object(cert_utils, 'related_units')
    @mock.patch.object(cert_utils, 'relation_ids')
    @mock.patch.object(cert_utils, 'relation_get')
    def test_get_requests_for_local_unit(self, relation_get, relation_ids,
                                         related_units, local_unit):
        local_unit.return_value = 'rabbitmq-server/2'
        relation_ids.return_value = ['certificates:12']
        related_units.return_value = ['vault/0']
        certs = {
            'juju-cd4bb3-5.lxd': {
                'cert': 'BASECERT',
                'key': 'BASEKEY'},
            'juju-cd4bb3-5.internal': {
                'cert': 'INTERNALCERT',
                'key': 'INTERNALKEY'}}
        _relation_info = {
            'rabbitmq-server_2.processed_requests': json.dumps(certs),
            'chain': 'MYCHAIN',
            'ca': 'ROOTCA',
        }
        relation_get.return_value = _relation_info
        self.assertEqual(
            cert_utils.get_requests_for_local_unit(),
            [{
                'ca': 'ROOTCA',
                'certs': {
                    'juju-cd4bb3-5.lxd': {
                        'cert': 'BASECERT',
                        'key': 'BASEKEY'},
                    'juju-cd4bb3-5.internal': {
                        'cert': 'INTERNALCERT',
                        'key': 'INTERNALKEY'}},
                'chain': 'MYCHAIN'}]
        )

    @mock.patch.object(cert_utils, 'get_requests_for_local_unit')
    def test_get_bundle_for_cn(self, get_requests_for_local_unit):
        get_requests_for_local_unit.return_value = [{
            'ca': 'ROOTCA',
            'certs': {
                'juju-cd4bb3-5.lxd': {
                    'cert': 'BASECERT',
                    'key': 'BASEKEY'},
                'juju-cd4bb3-5.internal': {
                    'cert': 'INTERNALCERT',
                    'key': 'INTERNALKEY'}},
            'chain': 'MYCHAIN'}]
        self.assertEqual(
            cert_utils.get_bundle_for_cn('juju-cd4bb3-5.internal'),
            {
                'ca': 'ROOTCA',
                'cert': 'INTERNALCERT',
                'chain': 'MYCHAIN',
                'key': 'INTERNALKEY'})

    @mock.patch.object(cert_utils, 'local_unit', return_value='unit/2')
    @mock.patch.object(cert_utils, 'resolve_network_cidr')
    @mock.patch.object(cert_utils, 'get_vip_in_network')
    @mock.patch.object(cert_utils, 'get_relation_ip')
    @mock.patch.object(cert_utils, 'resolve_address')
    @mock.patch.object(cert_utils, 'config')
    @mock.patch.object(cert_utils, 'get_hostname')
    @mock.patch.object(cert_utils, 'local_address')
    def test_get_certificate_sans(self, local_address, get_hostname,
                                  config, resolve_address,
                                  get_relation_ip,
                                  get_vip_in_network, resolve_network_cidr,
                                  local_unit):
        local_address.return_value = '10.1.2.3'
        get_hostname.return_value = 'juju-unit-2'
        _config = {
            'os-internal-hostname': 'internal.openstack.local',
            'os-admin-hostname': 'admin.openstack.local',
            'os-public-hostname': 'public.openstack.local',
        }
        _resolve_address = {
            'int': '10.0.0.2',
            'internal': '10.0.0.2',
            'admin': '10.10.0.2',
            'public': '10.20.0.2',
        }
        _npa = {
            'internal': '10.0.0.3',
            'admin': '10.10.0.3',
            'public': '10.20.0.3',
        }
        _vips = {
            '10.0.0.0/16': '10.0.0.100',
            '10.10.0.0/16': '10.10.0.100',
            '10.20.0.0/16': '10.20.0.100',
        }
        _resolve_nets = {
            '10.0.0.3': '10.0.0.0/16',
            '10.10.0.3': '10.10.0.0/16',
            '10.20.0.3': '10.20.0.0/16',
        }
        expect = list(set([
            '10.10.0.100', '10.10.0.2', '10.10.0.3',
            '10.0.0.100', '10.0.0.2', '10.0.0.3',
            '10.1.2.3',
            '10.20.0.100', '10.20.0.2', '10.20.0.3']))
        self.maxDiff = None
        config.side_effect = lambda x: _config.get(x)
        get_vip_in_network.side_effect = lambda x: _vips.get(x)
        resolve_network_cidr.side_effect = lambda x: _resolve_nets.get(x)
        get_relation_ip.side_effect = lambda x, cidr_network: _npa.get(x)
        resolve_address.side_effect = \
            lambda endpoint_type: _resolve_address[endpoint_type]
        expected_get_relation_ip_calls = [
            mock.call('internal', cidr_network=None),
            mock.call('admin', cidr_network=None),
            mock.call('public', cidr_network=None)]
        self.assertEqual(cert_utils.get_certificate_sans().sort(),
                         expect.sort())
        get_relation_ip.assert_has_calls(
            expected_get_relation_ip_calls, any_order=True)

    @mock.patch.object(cert_utils, 'local_unit', return_value='unit/2')
    @mock.patch.object(cert_utils, 'resolve_network_cidr')
    @mock.patch.object(cert_utils, 'get_vip_in_network')
    @mock.patch.object(cert_utils, 'get_relation_ip')
    @mock.patch.object(cert_utils, 'resolve_address')
    @mock.patch.object(cert_utils, 'config')
    @mock.patch.object(cert_utils, 'get_hostname')
    @mock.patch.object(cert_utils, 'local_address')
    def test_get_certificate_sans_bindings(
            self, local_address, get_hostname, config, resolve_address,
            get_relation_ip, get_vip_in_network, resolve_network_cidr, local_unit):
        local_address.return_value = '10.1.2.3'
        get_hostname.return_value = 'juju-unit-2'
        _config = {
            'os-internal-hostname': 'internal.openstack.local',
            'os-admin-hostname': 'admin.openstack.local',
            'os-public-hostname': 'public.openstack.local',
        }
        _resolve_address = {
            'int': '10.0.0.2',
            'internal': '10.0.0.2',
            'admin': '10.10.0.2',
            'public': '10.20.0.2',
        }
        _npa = {
            'internal': '10.0.0.3',
            'admin': '10.10.0.3',
            'public': '10.20.0.3',
        }
        _vips = {
            '10.0.0.0/16': '10.0.0.100',
            '10.10.0.0/16': '10.10.0.100',
            '10.20.0.0/16': '10.20.0.100',
        }
        _resolve_nets = {
            '10.0.0.3': '10.0.0.0/16',
            '10.10.0.3': '10.10.0.0/16',
            '10.20.0.3': '10.20.0.0/16',
        }
        expect = list(set([
            '10.10.0.100', '10.10.0.2', '10.10.0.3',
            '10.0.0.100', '10.0.0.2', '10.0.0.3',
            '10.1.2.3',
            '10.20.0.100', '10.20.0.2', '10.20.0.3']))
        self.maxDiff = None
        config.side_effect = lambda x: _config.get(x)
        get_vip_in_network.side_effect = lambda x: _vips.get(x)
        resolve_network_cidr.side_effect = lambda x: _resolve_nets.get(x)
        get_relation_ip.side_effect = lambda x, cidr_network: _npa.get(x)
        resolve_address.side_effect = \
            lambda endpoint_type: _resolve_address[endpoint_type]
        expected_get_relation_ip_calls = [
            mock.call('internal', cidr_network=None),
            mock.call('admin', cidr_network=None),
            mock.call('public', cidr_network=None),
            mock.call('mybinding', cidr_network=None)]
        self.assertEqual(
            cert_utils.get_certificate_sans(bindings=['mybinding']).sort(),
            expect.sort())
        get_relation_ip.assert_has_calls(
            expected_get_relation_ip_calls, any_order=True)
