# Copyright 2017 Rackspace US, Inc.
#
# 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.
import random
from unittest import mock

from oslo_utils import uuidutils

from octavia.amphorae.backends.agent import api_server
from octavia.amphorae.backends.agent.api_server import amphora_info
from octavia.amphorae.backends.agent.api_server import util
from octavia.common.jinja.haproxy.combined_listeners import jinja_cfg
from octavia.tests.common import utils as test_utils
import octavia.tests.unit.base as base
from octavia.tests.unit.common.sample_configs import sample_configs_combined


class TestAmphoraInfo(base.TestCase):

    API_VERSION = random.randrange(0, 10000)
    BASE_AMP_PATH = '/var/lib/octavia'
    BASE_CRT_PATH = BASE_AMP_PATH + '/certs'
    HAPROXY_VERSION = random.randrange(0, 10000)
    KEEPALIVED_VERSION = random.randrange(0, 10000)
    IPVSADM_VERSION = random.randrange(0, 10000)
    FAKE_LISTENER_ID_1 = uuidutils.generate_uuid()
    FAKE_LISTENER_ID_2 = uuidutils.generate_uuid()
    FAKE_LISTENER_ID_3 = uuidutils.generate_uuid()
    FAKE_LISTENER_ID_4 = uuidutils.generate_uuid()
    LB_ID_1 = uuidutils.generate_uuid()

    def setUp(self):
        super(TestAmphoraInfo, self).setUp()
        self.osutils_mock = mock.MagicMock()
        self.amp_info = amphora_info.AmphoraInfo(self.osutils_mock)
        self.udp_driver = mock.MagicMock()

        # setup a fake haproxy config file
        templater = jinja_cfg.JinjaTemplater(
            base_amp_path=self.BASE_AMP_PATH,
            base_crt_dir=self.BASE_CRT_PATH)
        tls_tupel = {'cont_id_1':
                     sample_configs_combined.sample_tls_container_tuple(
                         id='tls_container_id',
                         certificate='imaCert1', private_key='imaPrivateKey1',
                         primary_cn='FakeCN')}
        self.rendered_haproxy_cfg = templater.render_loadbalancer_obj(
            sample_configs_combined.sample_amphora_tuple(),
            [sample_configs_combined.sample_listener_tuple(
                proto='TERMINATED_HTTPS', tls=True, sni=True)],
            tls_tupel)
        path = util.config_path(self.LB_ID_1)
        self.useFixture(test_utils.OpenFixture(path,
                                               self.rendered_haproxy_cfg))

    def _return_version(self, package_name):
        if package_name == 'ipvsadm':
            return self.IPVSADM_VERSION
        elif package_name == 'keepalived':
            return self.KEEPALIVED_VERSION
        else:
            return self.HAPROXY_VERSION

    @mock.patch.object(amphora_info, "webob")
    @mock.patch('octavia.amphorae.backends.agent.api_server.'
                'amphora_info.AmphoraInfo._get_version_of_installed_package',
                return_value=HAPROXY_VERSION)
    @mock.patch('socket.gethostname', return_value='FAKE_HOST')
    def test_compile_amphora_info(self, mock_gethostname, mock_pkg_version,
                                  mock_webob):
        original_version = api_server.VERSION
        api_server.VERSION = self.API_VERSION
        expected_dict = {'api_version': self.API_VERSION,
                         'hostname': 'FAKE_HOST',
                         'haproxy_version': self.HAPROXY_VERSION}
        self.amp_info.compile_amphora_info()
        mock_webob.Response.assert_called_once_with(json=expected_dict)
        api_server.VERSION = original_version

    @mock.patch.object(amphora_info, "webob")
    @mock.patch('octavia.amphorae.backends.agent.api_server.'
                'amphora_info.AmphoraInfo._get_version_of_installed_package')
    @mock.patch('socket.gethostname', return_value='FAKE_HOST')
    def test_compile_amphora_info_for_udp(self, mock_gethostname,
                                          mock_pkg_version, mock_webob):

        mock_pkg_version.side_effect = self._return_version
        self.udp_driver.get_subscribed_amp_compile_info.side_effect = [
            ['keepalived', 'ipvsadm']]
        original_version = api_server.VERSION
        api_server.VERSION = self.API_VERSION
        expected_dict = {'api_version': self.API_VERSION,
                         'hostname': 'FAKE_HOST',
                         'haproxy_version': self.HAPROXY_VERSION,
                         'keepalived_version': self.KEEPALIVED_VERSION,
                         'ipvsadm_version': self.IPVSADM_VERSION
                         }
        self.amp_info.compile_amphora_info(extend_udp_driver=self.udp_driver)
        mock_webob.Response.assert_called_once_with(json=expected_dict)
        api_server.VERSION = original_version

    @mock.patch('octavia.amphorae.backends.agent.api_server.util.'
                'get_listeners', return_value=[FAKE_LISTENER_ID_1,
                                               FAKE_LISTENER_ID_2])
    @mock.patch('octavia.amphorae.backends.agent.api_server.'
                'amphora_info.AmphoraInfo._get_meminfo')
    @mock.patch('octavia.amphorae.backends.agent.api_server.'
                'amphora_info.AmphoraInfo._cpu')
    @mock.patch('os.statvfs')
    @mock.patch('octavia.amphorae.backends.agent.api_server.'
                'amphora_info.AmphoraInfo._get_networks')
    @mock.patch('octavia.amphorae.backends.agent.api_server.'
                'amphora_info.AmphoraInfo._load')
    @mock.patch('octavia.amphorae.backends.agent.api_server.'
                'amphora_info.AmphoraInfo._get_version_of_installed_package')
    @mock.patch('octavia.amphorae.backends.agent.api_server.'
                'amphora_info.AmphoraInfo._count_haproxy_processes')
    @mock.patch('socket.gethostname', return_value='FAKE_HOST')
    def test_compile_amphora_details(self, mhostname, m_count, m_pkg_version,
                                     m_load, m_get_nets, m_os, m_cpu,
                                     mget_mem, mget_listener):
        mget_mem.return_value = {'SwapCached': 0, 'Buffers': 344792,
                                 'MemTotal': 21692784, 'Cached': 4271856,
                                 'Slab': 534384, 'MemFree': 12685624,
                                 'Shmem': 9520}
        m_cpu.return_value = {'user': '252551', 'softirq': '8336',
                              'system': '52554', 'total': 7503411}
        m_pkg_version.side_effect = self._return_version
        mdisk_info = mock.MagicMock()
        m_os.return_value = mdisk_info
        mdisk_info.f_blocks = 34676992
        mdisk_info.f_bfree = 28398016
        mdisk_info.f_frsize = 4096
        mdisk_info.f_bavail = 26630646
        m_get_nets.return_value = {'eth1': {'network_rx': 996,
                                            'network_tx': 418},
                                   'eth2': {'network_rx': 848,
                                            'network_tx': 578}}
        m_load.return_value = ['0.09', '0.11', '0.10']
        m_count.return_value = 5
        original_version = api_server.VERSION
        api_server.VERSION = self.API_VERSION
        expected_dict = {u'active': True,
                         u'api_version': self.API_VERSION,
                         u'cpu': {u'soft_irq': u'8336',
                                  u'system': u'52554',
                                  u'total': 7503411,
                                  u'user': u'252551'},
                         u'disk': {u'available': 109079126016,
                                   u'used': 25718685696},
                         u'haproxy_count': 5,
                         u'haproxy_version': self.HAPROXY_VERSION,
                         u'hostname': u'FAKE_HOST',
                         u'listeners': sorted([self.FAKE_LISTENER_ID_1,
                                               self.FAKE_LISTENER_ID_2]),
                         u'load': [u'0.09', u'0.11', u'0.10'],
                         u'memory': {u'buffers': 344792,
                                     u'cached': 4271856,
                                     u'free': 12685624,
                                     u'shared': 9520,
                                     u'slab': 534384,
                                     u'swap_used': 0,
                                     u'total': 21692784},
                         u'networks': {u'eth1': {u'network_rx': 996,
                                                 u'network_tx': 418},
                                       u'eth2': {u'network_rx': 848,
                                                 u'network_tx': 578}},
                         u'packages': {},
                         u'topology': u'SINGLE',
                         u'topology_status': u'OK'}
        actual = self.amp_info.compile_amphora_details()
        self.assertEqual(expected_dict, actual.json)
        api_server.VERSION = original_version

    @mock.patch('octavia.amphorae.backends.agent.api_server.util.'
                'get_udp_listeners',
                return_value=[FAKE_LISTENER_ID_3, FAKE_LISTENER_ID_4])
    @mock.patch('octavia.amphorae.backends.agent.api_server.util.'
                'get_loadbalancers')
    @mock.patch('octavia.amphorae.backends.agent.api_server.'
                'amphora_info.AmphoraInfo._get_meminfo')
    @mock.patch('octavia.amphorae.backends.agent.api_server.'
                'amphora_info.AmphoraInfo._cpu')
    @mock.patch('os.statvfs')
    @mock.patch('octavia.amphorae.backends.agent.api_server.'
                'amphora_info.AmphoraInfo._get_networks')
    @mock.patch('octavia.amphorae.backends.agent.api_server.'
                'amphora_info.AmphoraInfo._load')
    @mock.patch('octavia.amphorae.backends.agent.api_server.'
                'amphora_info.AmphoraInfo._get_version_of_installed_package')
    @mock.patch('octavia.amphorae.backends.agent.api_server.'
                'amphora_info.AmphoraInfo._count_haproxy_processes')
    @mock.patch('socket.gethostname', return_value='FAKE_HOST')
    def test_compile_amphora_details_for_udp(self, mhostname, m_count,
                                             m_pkg_version, m_load, m_get_nets,
                                             m_os, m_cpu, mget_mem,
                                             mock_get_lb, mget_udp_listener):
        mget_mem.return_value = {'SwapCached': 0, 'Buffers': 344792,
                                 'MemTotal': 21692784, 'Cached': 4271856,
                                 'Slab': 534384, 'MemFree': 12685624,
                                 'Shmem': 9520}
        m_cpu.return_value = {'user': '252551', 'softirq': '8336',
                              'system': '52554', 'total': 7503411}
        m_pkg_version.side_effect = self._return_version
        mdisk_info = mock.MagicMock()
        m_os.return_value = mdisk_info
        mdisk_info.f_blocks = 34676992
        mdisk_info.f_bfree = 28398016
        mdisk_info.f_frsize = 4096
        mdisk_info.f_bavail = 26630646
        m_get_nets.return_value = {'eth1': {'network_rx': 996,
                                            'network_tx': 418},
                                   'eth2': {'network_rx': 848,
                                            'network_tx': 578}}
        m_load.return_value = ['0.09', '0.11', '0.10']
        m_count.return_value = 5
        self.udp_driver.get_subscribed_amp_compile_info.return_value = [
            'keepalived', 'ipvsadm']
        self.udp_driver.is_listener_running.side_effect = [True, False]
        mock_get_lb.return_value = [self.LB_ID_1]
        original_version = api_server.VERSION
        api_server.VERSION = self.API_VERSION
        expected_dict = {u'active': True,
                         u'api_version': self.API_VERSION,
                         u'cpu': {u'soft_irq': u'8336',
                                  u'system': u'52554',
                                  u'total': 7503411,
                                  u'user': u'252551'},
                         u'disk': {u'available': 109079126016,
                                   u'used': 25718685696},
                         u'haproxy_count': 5,
                         u'haproxy_version': self.HAPROXY_VERSION,
                         u'keepalived_version': self.KEEPALIVED_VERSION,
                         u'ipvsadm_version': self.IPVSADM_VERSION,
                         u'udp_listener_process_count': 1,
                         u'hostname': u'FAKE_HOST',
                         u'listeners': sorted(list(set(
                             [self.FAKE_LISTENER_ID_3,
                              self.FAKE_LISTENER_ID_4,
                              'sample_listener_id_1']))),
                         u'load': [u'0.09', u'0.11', u'0.10'],
                         u'memory': {u'buffers': 344792,
                                     u'cached': 4271856,
                                     u'free': 12685624,
                                     u'shared': 9520,
                                     u'slab': 534384,
                                     u'swap_used': 0,
                                     u'total': 21692784},
                         u'networks': {u'eth1': {u'network_rx': 996,
                                                 u'network_tx': 418},
                                       u'eth2': {u'network_rx': 848,
                                                 u'network_tx': 578}},
                         u'packages': {},
                         u'topology': u'SINGLE',
                         u'topology_status': u'OK'}
        actual = self.amp_info.compile_amphora_details(self.udp_driver)
        self.assertEqual(expected_dict, actual.json)
        api_server.VERSION = original_version

    @mock.patch('octavia.amphorae.backends.agent.api_server.util.'
                'is_lb_running')
    def test__count_haproxy_process(self, mock_is_running):

        # Test no listeners passed in
        result = self.amp_info._count_haproxy_processes([])
        self.assertEqual(0, result)

        # Test with a listener specified
        mock_is_running.side_effect = [True, False]
        result = self.amp_info._count_haproxy_processes(
            [uuidutils.generate_uuid(), uuidutils.generate_uuid()])
        self.assertEqual(1, result)

    def test__count_udp_listener_processes(self):
        self.udp_driver.is_listener_running.side_effect = [True, False, True]
        expected = 2
        actual = self.amp_info._count_udp_listener_processes(
            self.udp_driver, [self.FAKE_LISTENER_ID_1,
                              self.FAKE_LISTENER_ID_2,
                              self.FAKE_LISTENER_ID_3])
        self.assertEqual(expected, actual)

    @mock.patch('octavia.amphorae.backends.agent.api_server.'
                'amphora_info.AmphoraInfo._get_version_of_installed_package')
    def test__get_extend_body_from_udp_driver(self, m_get_version):
        self.udp_driver.get_subscribed_amp_compile_info.return_value = [
            'keepalived', 'ipvsadm']
        m_get_version.side_effect = self._return_version
        expected = {
            "keepalived_version": self.KEEPALIVED_VERSION,
            "ipvsadm_version": self.IPVSADM_VERSION
        }
        actual = self.amp_info._get_extend_body_from_udp_driver(
            self.udp_driver)
        self.assertEqual(expected, actual)

    def test__get_meminfo(self):
        # Known data test
        meminfo = ('MemTotal:       21692784 kB\n'
                   'MemFree:        12685624 kB\n'
                   'MemAvailable:   17384072 kB\n'
                   'Buffers:          344792 kB\n'
                   'Cached:          4271856 kB\n'
                   'SwapCached:            0 kB\n'
                   'Active:          5808816 kB\n'
                   'Inactive:        2445236 kB\n'
                   'Active(anon):    3646184 kB\n'
                   'Inactive(anon):     8672 kB\n'
                   'Active(file):    2162632 kB\n'
                   'Inactive(file):  2436564 kB\n'
                   'Unevictable:       52664 kB\n'
                   'Mlocked:           52664 kB\n'
                   'SwapTotal:      20476924 kB\n'
                   'SwapFree:       20476924 kB\n'
                   'Dirty:                92 kB\n'
                   'Writeback:             0 kB\n'
                   'AnonPages:       3690088 kB\n'
                   'Mapped:           108520 kB\n'
                   'Shmem:              9520 kB\n'
                   'Slab:             534384 kB\n'
                   'SReclaimable:     458160 kB\n'
                   'SUnreclaim:        76224 kB\n'
                   'KernelStack:       11776 kB\n'
                   'PageTables:        33088 kB\n'
                   'NFS_Unstable:          0 kB\n'
                   'Bounce:                0 kB\n'
                   'WritebackTmp:          0 kB\n'
                   'CommitLimit:    31323316 kB\n'
                   'Committed_AS:    6930732 kB\n'
                   'VmallocTotal:   34359738367 kB\n'
                   'VmallocUsed:           0 kB\n'
                   'VmallocChunk:          0 kB\n'
                   'HardwareCorrupted:     0 kB\n'
                   'AnonHugePages:   1400832 kB\n'
                   'CmaTotal:              0 kB\n'
                   'CmaFree:               0 kB\n'
                   'HugePages_Total:       0\n'
                   'HugePages_Free:        0\n'
                   'HugePages_Rsvd:        0\n'
                   'HugePages_Surp:        0\n'
                   'Hugepagesize:       2048 kB\n'
                   'DirectMap4k:      130880 kB\n'
                   'DirectMap2M:     8376320 kB\n'
                   'DirectMap1G:    14680064 kB\n')

        self.useFixture(test_utils.OpenFixture('/proc/meminfo',
                                               contents=meminfo))

        expected_result = {'SwapCached': 0, 'DirectMap2M': 8376320,
                           'CmaTotal': 0, 'Inactive': 2445236,
                           'KernelStack': 11776, 'SwapTotal': 20476924,
                           'VmallocUsed': 0, 'Buffers': 344792,
                           'MemTotal': 21692784, 'Mlocked': 52664,
                           'Cached': 4271856, 'AnonPages': 3690088,
                           'Unevictable': 52664, 'SUnreclaim': 76224,
                           'MemFree': 12685624, 'Writeback': 0,
                           'NFS_Unstable': 0, 'VmallocTotal': 34359738367,
                           'MemAvailable': 17384072, 'CmaFree': 0,
                           'SwapFree': 20476924, 'AnonHugePages': 1400832,
                           'DirectMap1G': 14680064, 'Hugepagesize': 2048,
                           'Dirty': 92, 'Bounce': 0, 'PageTables': 33088,
                           'SReclaimable': 458160, 'Active': 5808816,
                           'Mapped': 108520, 'Slab': 534384,
                           'Active(anon)': 3646184, 'VmallocChunk': 0,
                           'Inactive(file)': 2436564, 'WritebackTmp': 0,
                           'Shmem': 9520, 'Inactive(anon)': 8672,
                           'HardwareCorrupted': 0, 'Active(file)': 2162632,
                           'DirectMap4k': 130880, 'Committed_AS': 6930732,
                           'CommitLimit': 31323316}

        result = self.amp_info._get_meminfo()
        self.assertEqual(expected_result, result)

    def test__cpu(self):

        sample_stat = 'cpu  252551 802 52554 7181757 7411 0 8336 0 0 0'

        expected_result = {'user': '252551', 'iowait': '7411', 'nice': '802',
                           'softirq': '8336', 'idle': '7181757',
                           'system': '52554', 'total': 7503411, 'irq': '0'}

        self.useFixture(test_utils.OpenFixture('/proc/stat',
                                               contents=sample_stat))

        result = self.amp_info._cpu()

        self.assertEqual(expected_result, result)

    def test__load(self):

        sample_loadavg = '0.09 0.11 0.10 2/630 15346'

        expected_result = ['0.09', '0.11', '0.10']

        self.useFixture(test_utils.OpenFixture('/proc/loadavg',
                                               contents=sample_loadavg))

        result = self.amp_info._load()

        self.assertEqual(expected_result, result)

    @mock.patch('pyroute2.NetNS', create=True)
    def test__get_networks(self, mock_netns):

        # The output of get_links is huge, just pulling out the parts we
        # care about for this test.
        sample_get_links_minimal = [
            {'attrs': [('IFLA_IFNAME', 'lo')]},
            {'attrs': [('IFLA_IFNAME', 'eth1'),
                       ('IFLA_STATS64', {'tx_bytes': 418, 'rx_bytes': 996})]},
            {'attrs': [('IFLA_IFNAME', 'eth2'),
                       ('IFLA_STATS64', {'tx_bytes': 578, 'rx_bytes': 848})]},
            {'attrs': [('IFLA_IFNAME', 'eth3')]}]

        netns_handle = mock_netns.return_value.__enter__.return_value
        netns_handle.get_links.return_value = sample_get_links_minimal

        expected_result = {'eth1': {'network_rx': 996, 'network_tx': 418},
                           'eth2': {'network_rx': 848, 'network_tx': 578}}

        result = self.amp_info._get_networks()

        self.assertEqual(expected_result, result)
