import subprocess
import unittest

from mock import patch, call, MagicMock

import charmhelpers.contrib.network.ovs as ovs

from tests.helpers import patch_open


# NOTE(fnordahl): some functions drectly under the ``contrib.network.ovs``
# module have their unit tests in the ``test_ovs.py`` module in the
# ``tests.contrib.network.ovs`` package.


GOOD_CERT = '''Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number: 13798680962510501282 (0xbf7ec33a136235a2)
    Signature Algorithm: sha1WithRSAEncryption
        Issuer: C=US, ST=CA, L=Palo Alto, O=Open vSwitch, OU=Open vSwitch
        Validity
            Not Before: Jun 28 17:02:19 2013 GMT
            Not After : Jun 28 17:02:19 2019 GMT
        Subject: C=US, ST=CA, L=Palo Alto, O=Open vSwitch, OU=Open vSwitch
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:e8:a7:db:0a:6d:c0:16:4a:14:96:1d:74:91:15:
                    64:3f:ae:2a:54:be:2a:fe:10:14:9a:73:39:d8:58:
                    74:7f:ab:d5:f2:39:aa:9a:27:7c:31:82:f8:74:42:
                    46:8d:c5:3b:42:55:52:be:75:7f:a5:b1:ec:d5:29:
                    9f:62:0e:de:31:27:2b:95:1f:24:0d:ca:8c:48:30:
                    96:9f:ba:b7:9d:eb:c1:bd:93:05:e3:d8:ca:66:5a:
                    e9:cb:a5:7a:3a:8d:27:e2:05:9d:88:fc:a9:ef:af:
                    47:4c:66:ce:c6:43:73:1a:85:f4:5f:b9:53:5b:29:
                    f3:c3:23:1f:0c:20:95:11:50:71:b2:f6:01:23:3f:
                    66:0f:5c:43:c2:90:fb:e5:98:73:98:e9:38:bb:1f:
                    1b:89:97:1e:dc:d7:98:07:68:32:ec:da:1d:69:0b:
                    e2:df:40:fb:64:52:e5:e9:40:27:b0:ca:73:21:51:
                    f6:8f:00:20:c0:2b:1a:d4:01:c2:32:38:9d:d1:8d:
                    88:71:46:a9:42:0d:ee:3b:1c:88:db:27:69:49:f9:
                    60:34:70:61:3d:60:df:7e:e4:e1:1d:c6:16:89:05:
                    ba:31:06:eb:88:b5:78:94:5d:8c:9d:88:fe:f2:c2:
                    80:a1:04:15:d3:84:85:d3:aa:5a:1d:53:5c:f8:57:
                    ae:61
                Exponent: 65537 (0x10001)
    Signature Algorithm: sha1WithRSAEncryption
         14:7e:ca:c3:fc:93:60:9f:80:e0:65:2e:ef:41:2d:f9:af:77:
         da:6d:e2:e0:11:70:17:fb:e5:67:4c:f0:ad:39:ec:96:ef:fe:
         d5:95:94:70:e5:52:31:68:63:8c:ea:b3:a1:8e:02:e2:91:4b:
         a8:8c:07:86:fd:80:98:a2:b1:90:2b:9c:2e:ab:f4:73:9d:8f:
         fd:31:b9:8f:fe:6c:af:d6:bf:72:44:89:08:93:19:ef:2b:c3:
         7c:ab:ba:bc:57:ca:f1:17:e4:e8:81:40:ca:65:df:84:be:10:
         2c:42:46:af:d2:e0:0d:df:5d:56:53:65:13:e0:20:55:b4:ee:
         cd:5e:b5:c4:97:1d:3e:a6:c1:9c:7e:b8:87:ee:64:78:a5:59:
         e5:b2:79:47:9a:8e:59:fa:c4:18:ea:27:fd:a2:d5:76:d0:ae:
         d9:05:f6:0e:23:ca:7d:66:a1:ba:18:67:f5:6d:bb:51:5a:f5:
         52:e9:17:bb:63:15:24:b4:61:25:9f:d9:9c:89:58:93:9a:c3:
         74:55:72:3e:f9:ff:ef:54:7d:e8:28:78:ba:3c:c7:15:ba:b9:
         c6:e3:8c:61:cb:a9:ed:8d:07:16:0d:8d:f6:1c:36:11:69:08:
         b8:45:7d:fc:fd:d1:ab:2d:9b:4e:9c:dd:11:78:50:c7:87:9f:
         4a:24:9c:a0
-----BEGIN CERTIFICATE-----
MIIDwjCCAqoCCQC/fsM6E2I1ojANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMC
VVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlQYWxvIEFsdG8xFTATBgNVBAoTDE9w
ZW4gdlN3aXRjaDEfMB0GA1UECxMWT3BlbiB2U3dpdGNoIGNlcnRpZmllcjE6MDgG
A1UEAxMxb3ZzY2xpZW50IGlkOjU4MTQ5N2E1LWJjMDAtNGVjYy1iNzkwLTU3NTZj
ZWUxNmE0ODAeFw0xMzA2MjgxNzAyMTlaFw0xOTA2MjgxNzAyMTlaMIGiMQswCQYD
VQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVBhbG8gQWx0bzEVMBMGA1UE
ChMMT3BlbiB2U3dpdGNoMR8wHQYDVQQLExZPcGVuIHZTd2l0Y2ggY2VydGlmaWVy
MTowOAYDVQQDEzFvdnNjbGllbnQgaWQ6NTgxNDk3YTUtYmMwMC00ZWNjLWI3OTAt
NTc1NmNlZTE2YTQ4MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6Kfb
Cm3AFkoUlh10kRVkP64qVL4q/hAUmnM52Fh0f6vV8jmqmid8MYL4dEJGjcU7QlVS
vnV/pbHs1SmfYg7eMScrlR8kDcqMSDCWn7q3nevBvZMF49jKZlrpy6V6Oo0n4gWd
iPyp769HTGbOxkNzGoX0X7lTWynzwyMfDCCVEVBxsvYBIz9mD1xDwpD75ZhzmOk4
ux8biZce3NeYB2gy7NodaQvi30D7ZFLl6UAnsMpzIVH2jwAgwCsa1AHCMjid0Y2I
cUapQg3uOxyI2ydpSflgNHBhPWDffuThHcYWiQW6MQbriLV4lF2MnYj+8sKAoQQV
04SF06paHVNc+FeuYQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQAUfsrD/JNgn4Dg
ZS7vQS35r3fabeLgEXAX++VnTPCtOeyW7/7VlZRw5VIxaGOM6rOhjgLikUuojAeG
/YCYorGQK5wuq/RznY/9MbmP/myv1r9yRIkIkxnvK8N8q7q8V8rxF+TogUDKZd+E
vhAsQkav0uAN311WU2UT4CBVtO7NXrXElx0+psGcfriH7mR4pVnlsnlHmo5Z+sQY
6if9otV20K7ZBfYOI8p9ZqG6GGf1bbtRWvVS6Re7YxUktGEln9mciViTmsN0VXI+
+f/vVH3oKHi6PMcVurnG44xhy6ntjQcWDY32HDYRaQi4RX38/dGrLZtOnN0ReFDH
h59KJJyg
-----END CERTIFICATE-----
'''

PEM_ENCODED = '''-----BEGIN CERTIFICATE-----
MIIDwjCCAqoCCQC/fsM6E2I1ojANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMC
VVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlQYWxvIEFsdG8xFTATBgNVBAoTDE9w
ZW4gdlN3aXRjaDEfMB0GA1UECxMWT3BlbiB2U3dpdGNoIGNlcnRpZmllcjE6MDgG
A1UEAxMxb3ZzY2xpZW50IGlkOjU4MTQ5N2E1LWJjMDAtNGVjYy1iNzkwLTU3NTZj
ZWUxNmE0ODAeFw0xMzA2MjgxNzAyMTlaFw0xOTA2MjgxNzAyMTlaMIGiMQswCQYD
VQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVBhbG8gQWx0bzEVMBMGA1UE
ChMMT3BlbiB2U3dpdGNoMR8wHQYDVQQLExZPcGVuIHZTd2l0Y2ggY2VydGlmaWVy
MTowOAYDVQQDEzFvdnNjbGllbnQgaWQ6NTgxNDk3YTUtYmMwMC00ZWNjLWI3OTAt
NTc1NmNlZTE2YTQ4MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6Kfb
Cm3AFkoUlh10kRVkP64qVL4q/hAUmnM52Fh0f6vV8jmqmid8MYL4dEJGjcU7QlVS
vnV/pbHs1SmfYg7eMScrlR8kDcqMSDCWn7q3nevBvZMF49jKZlrpy6V6Oo0n4gWd
iPyp769HTGbOxkNzGoX0X7lTWynzwyMfDCCVEVBxsvYBIz9mD1xDwpD75ZhzmOk4
ux8biZce3NeYB2gy7NodaQvi30D7ZFLl6UAnsMpzIVH2jwAgwCsa1AHCMjid0Y2I
cUapQg3uOxyI2ydpSflgNHBhPWDffuThHcYWiQW6MQbriLV4lF2MnYj+8sKAoQQV
04SF06paHVNc+FeuYQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQAUfsrD/JNgn4Dg
ZS7vQS35r3fabeLgEXAX++VnTPCtOeyW7/7VlZRw5VIxaGOM6rOhjgLikUuojAeG
/YCYorGQK5wuq/RznY/9MbmP/myv1r9yRIkIkxnvK8N8q7q8V8rxF+TogUDKZd+E
vhAsQkav0uAN311WU2UT4CBVtO7NXrXElx0+psGcfriH7mR4pVnlsnlHmo5Z+sQY
6if9otV20K7ZBfYOI8p9ZqG6GGf1bbtRWvVS6Re7YxUktGEln9mciViTmsN0VXI+
+f/vVH3oKHi6PMcVurnG44xhy6ntjQcWDY32HDYRaQi4RX38/dGrLZtOnN0ReFDH
h59KJJyg
-----END CERTIFICATE-----'''

BAD_CERT = ''' NO MARKERS '''
TO_PATCH = [
    "apt_install",
    "log",
    "hashlib",
]


class OVSHelpersTest(unittest.TestCase):

    def setUp(self):
        for m in TO_PATCH:
            setattr(self, m, self._patch(m))

    def _patch(self, method):
        _m = patch('charmhelpers.contrib.network.ovs.' + method)
        mock = _m.start()
        self.addCleanup(_m.stop)
        return mock

    @patch('subprocess.check_output')
    def test_get_bridges(self, check_output):
        check_output.return_value = b"br1\n br2  "
        self.assertEqual(ovs.get_bridges(), ['br1', 'br2'])
        check_output.assert_called_once_with(['ovs-vsctl', 'list-br'])

    @patch('subprocess.check_output')
    def test_get_bridge_ports(self, check_output):
        check_output.return_value = b"p1\n p2  \np3"
        self.assertEqual(ovs.get_bridge_ports('br1'), ['p1', 'p2', 'p3'])
        check_output.assert_called_once_with(
            ['ovs-vsctl', '--', 'list-ports', 'br1'])

    @patch.object(ovs, 'get_bridges')
    @patch.object(ovs, 'get_bridge_ports')
    def test_get_bridges_and_ports_map(self, get_bridge_ports, get_bridges):
        get_bridges.return_value = ['br1', 'br2']
        get_bridge_ports.side_effect = [
            ['p1', 'p2'],
            ['p3']]
        self.assertEqual(ovs.get_bridges_and_ports_map(), {
            'br1': ['p1', 'p2'],
            'br2': ['p3'],
        })

    @patch('subprocess.check_call')
    def test_del_bridge(self, check_call):
        ovs.del_bridge('test')
        check_call.assert_called_with(["ovs-vsctl", "--", "--if-exists",
                                       "del-br", 'test'])
        self.assertTrue(self.log.call_count == 1)

    @patch('subprocess.check_call')
    def test_del_bridge_port(self, check_call):
        ovs.del_bridge_port('test', 'eth1')
        check_call.assert_has_calls([
            call(["ovs-vsctl", "--", "--if-exists", "del-port",
                  'test', 'eth1']),
            call(['ip', 'link', 'set', 'eth1', 'down']),
            call(['ip', 'link', 'set', 'eth1', 'promisc', 'off'])
        ])
        self.assertTrue(self.log.call_count == 1)

    @patch.object(ovs, 'port_to_br')
    @patch.object(ovs, 'add_bridge_port')
    @patch('subprocess.check_call')
    def test_add_ovsbridge_linuxbridge(self, check_call,
                                       add_bridge_port,
                                       port_to_br):
        port_to_br.return_value = None
        with patch_open() as (mock_open, mock_file):
            ovs.add_ovsbridge_linuxbridge('br-ex', 'br-eno1', ifdata={
                'external-ids': {'mycharm': 'br-ex'}
            })

        check_call.assert_called_with(['ifup', 'veth-br-eno1'])
        add_bridge_port.assert_called_with(
            'br-ex', 'veth-br-eno1', ifdata={
                'external-ids': {'mycharm': 'br-ex'}
            }
        )

    @patch.object(ovs, 'port_to_br')
    @patch.object(ovs, 'add_bridge_port')
    @patch('subprocess.check_call')
    def test_add_ovsbridge_linuxbridge_already_direct_wired(self,
                                                            check_call,
                                                            add_bridge_port,
                                                            port_to_br):
        port_to_br.return_value = 'br-ex'
        ovs.add_ovsbridge_linuxbridge('br-ex', 'br-eno1')
        check_call.assert_not_called()
        add_bridge_port.assert_not_called()

    @patch.object(ovs, 'port_to_br')
    @patch.object(ovs, 'add_bridge_port')
    @patch('subprocess.check_call')
    def test_add_ovsbridge_linuxbridge_longname(self, check_call,
                                                add_bridge_port,
                                                port_to_br):
        port_to_br.return_value = None
        mock_hasher = MagicMock()
        mock_hasher.hexdigest.return_value = '12345678901234578910'
        self.hashlib.sha256.return_value = mock_hasher
        with patch_open() as (mock_open, mock_file):
            ovs.add_ovsbridge_linuxbridge('br-ex', 'br-reallylongname')

        check_call.assert_called_with(['ifup', 'cvb12345678-10'])
        add_bridge_port.assert_called_with(
            'br-ex', 'cvb12345678-10', ifdata=None
        )

    @patch('os.path.exists')
    def test_is_linuxbridge_interface_false(self, exists):
        exists.return_value = False
        result = ovs.is_linuxbridge_interface('eno1')
        self.assertFalse(result)

    @patch('os.path.exists')
    def test_is_linuxbridge_interface_true(self, exists):
        exists.return_value = True
        result = ovs.is_linuxbridge_interface('eno1')
        self.assertTrue(result)

    @patch('subprocess.check_call')
    def test_set_manager(self, check_call):
        ovs.set_manager('manager')
        check_call.assert_called_with(['ovs-vsctl', 'set-manager',
                                       'ssl:manager'])
        self.assertTrue(self.log.call_count == 1)

    @patch('subprocess.check_call')
    def test_set_Open_vSwitch_column_value(self, check_call):
        ovs.set_Open_vSwitch_column_value('other_config:foo=bar')
        check_call.assert_called_with(['ovs-vsctl', 'set',
                                       'Open_vSwitch', '.', 'other_config:foo=bar'])
        self.assertTrue(self.log.call_count == 1)

    @patch('os.path.exists')
    def test_get_certificate_good_cert(self, exists):
        exists.return_value = True
        with patch_open() as (mock_open, mock_file):
            mock_file.read.return_value = GOOD_CERT
            self.assertEqual(ovs.get_certificate(), PEM_ENCODED)
        self.assertTrue(self.log.call_count == 1)

    @patch('os.path.exists')
    def test_get_certificate_bad_cert(self, exists):
        exists.return_value = True
        with patch_open() as (mock_open, mock_file):
            mock_file.read.return_value = BAD_CERT
            self.assertRaises(RuntimeError, ovs.get_certificate)
        self.assertTrue(self.log.call_count == 1)

    @patch('os.path.exists')
    def test_get_certificate_missing(self, exists):
        exists.return_value = False
        self.assertIsNone(ovs.get_certificate())
        self.assertTrue(self.log.call_count == 1)

    @patch('os.path.exists')
    @patch.object(ovs, 'service')
    def test_full_restart(self, service, exists):
        exists.return_value = False
        ovs.full_restart()
        service.assert_called_with('force-reload-kmod', 'openvswitch-switch')

    @patch('os.path.exists')
    @patch.object(ovs, 'service')
    def test_full_restart_upstart(self, service, exists):
        exists.return_value = True
        ovs.full_restart()
        service.assert_called_with('start', 'openvswitch-force-reload-kmod')

    @patch('subprocess.check_output')
    def test_port_to_br(self, check_output):
        check_output.return_value = b'br-ex'
        self.assertEqual(ovs.port_to_br('br-lb'),
                         'br-ex')

    @patch('subprocess.check_output')
    def test_port_to_br_not_found(self, check_output):
        check_output.side_effect = subprocess.CalledProcessError(1, 'not found')
        self.assertEqual(ovs.port_to_br('br-lb'), None)

    @patch('subprocess.check_call')
    def test_enable_ipfix_defaults(self, check_call):
        ovs.enable_ipfix('br-int',
                         '10.5.0.10:4739')
        check_call.assert_called_once_with([
            'ovs-vsctl', 'set', 'Bridge', 'br-int', 'ipfix=@i', '--',
            '--id=@i', 'create', 'IPFIX',
            'targets="10.5.0.10:4739"',
            'sampling=64',
            'cache_active_timeout=60',
            'cache_max_flows=128',
        ])

    @patch('subprocess.check_call')
    def test_enable_ipfix_values(self, check_call):
        ovs.enable_ipfix('br-int',
                         '10.5.0.10:4739',
                         sampling=120,
                         cache_max_flows=24,
                         cache_active_timeout=120)
        check_call.assert_called_once_with([
            'ovs-vsctl', 'set', 'Bridge', 'br-int', 'ipfix=@i', '--',
            '--id=@i', 'create', 'IPFIX',
            'targets="10.5.0.10:4739"',
            'sampling=120',
            'cache_active_timeout=120',
            'cache_max_flows=24',
        ])

    @patch('subprocess.check_call')
    def test_disable_ipfix(self, check_call):
        ovs.disable_ipfix('br-int')
        check_call.assert_called_once_with(
            ['ovs-vsctl', 'clear', 'Bridge', 'br-int', 'ipfix']
        )
