# Copyright 2014 Citrix Systems
#
#    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 contextlib

import mock
from neutron.common import exceptions
from neutron import context
from neutron import manager
from neutron.plugins.common import constants

from neutron_lbaas.db.loadbalancer import loadbalancer_db
from neutron_lbaas.services.loadbalancer.drivers.netscaler import ncc_client
from neutron_lbaas.services.loadbalancer.drivers.netscaler \
    import netscaler_driver
from neutron_lbaas.tests.unit.db.loadbalancer import test_db_loadbalancer


LBAAS_DRIVER_CLASS = ('neutron_lbaas.services.loadbalancer.drivers'
                      '.netscaler.netscaler_driver'
                      '.NetScalerPluginDriver')

NCC_CLIENT_CLASS = ('neutron_lbaas.services.loadbalancer.drivers'
                    '.netscaler.ncc_client'
                    '.NSClient')

LBAAS_PROVIDER_NAME = 'netscaler'
LBAAS_PROVIDER = ('LOADBALANCER:%s:%s:default' %
                  (LBAAS_PROVIDER_NAME, LBAAS_DRIVER_CLASS))

#Test data
TESTVIP_ID = '52ab5d71-6bb2-457f-8414-22a4ba55efec'
TESTPOOL_ID = 'da477c13-24cd-4c9f-8c19-757a61ef3b9d'
TESTMEMBER_ID = '84dea8bc-3416-4fb0-83f9-2ca6e7173bee'
TESTMONITOR_ID = '9b9245a2-0413-4f15-87ef-9a41ef66048c'

TESTVIP_PORT_ID = '327d9662-ade9-4c74-aaf6-c76f145c1180'
TESTPOOL_PORT_ID = '132c1dbb-d3d8-45aa-96e3-71f2ea51651e'
TESTPOOL_SNATIP_ADDRESS = '10.0.0.50'
TESTPOOL_SNAT_PORT = {
    'id': TESTPOOL_PORT_ID,
    'fixed_ips': [{'ip_address': TESTPOOL_SNATIP_ADDRESS}]
}
TESTVIP_IP = '10.0.1.100'
TESTMEMBER_IP = '10.0.0.5'


class TestLoadBalancerPluginBase(test_db_loadbalancer
                                 .LoadBalancerPluginDbTestCase):

    def setUp(self):
        # mock the NSClient class (REST client)
        client_mock_cls = mock.patch(NCC_CLIENT_CLASS).start()

        #mock the REST methods of the NSClient class
        self.client_mock_instance = client_mock_cls.return_value
        self.create_resource_mock = self.client_mock_instance.create_resource
        self.create_resource_mock.side_effect = mock_create_resource_func
        self.update_resource_mock = self.client_mock_instance.update_resource
        self.update_resource_mock.side_effect = mock_update_resource_func
        self.retrieve_resource_mock = (self.client_mock_instance
                                           .retrieve_resource)
        self.retrieve_resource_mock.side_effect = mock_retrieve_resource_func
        self.remove_resource_mock = self.client_mock_instance.remove_resource
        self.remove_resource_mock.side_effect = mock_remove_resource_func
        super(TestLoadBalancerPluginBase, self).setUp(
            lbaas_provider=LBAAS_PROVIDER)
        loaded_plugins = manager.NeutronManager().get_service_plugins()
        self.plugin_instance = loaded_plugins[constants.LOADBALANCER]


class TestNetScalerPluginDriver(TestLoadBalancerPluginBase):

    """Unit tests for the NetScaler LBaaS driver module."""

    def setUp(self):
        mock.patch.object(netscaler_driver, 'LOG').start()

        super(TestNetScalerPluginDriver, self).setUp()
        self.plugin_instance.drivers[LBAAS_PROVIDER_NAME] = (
            netscaler_driver.NetScalerPluginDriver(self.plugin_instance))
        self.driver = self.plugin_instance.drivers[LBAAS_PROVIDER_NAME]
        self.context = context.get_admin_context()

    def test_create_vip(self):
        with contextlib.nested(
            self.subnet(),
            mock.patch.object(self.driver.plugin._core_plugin, 'get_subnet')
        ) as (subnet, mock_get_subnet):
            mock_get_subnet.return_value = subnet['subnet']
            with self.pool(provider=LBAAS_PROVIDER_NAME) as pool:
                testvip = self._build_testvip_contents(subnet['subnet'],
                                                       pool['pool'])
                expectedvip = self._build_expectedvip_contents(
                    testvip,
                    subnet['subnet'])
                # mock the LBaaS plugin update_status().
                self._mock_update_status()
                # reset the create_resource() mock
                self.create_resource_mock.reset_mock()
                # execute the method under test
                self.driver.create_vip(self.context, testvip)
                # First, assert that create_resource was called once
                # with expected params.
                self.create_resource_mock.assert_called_once_with(
                    None,
                    netscaler_driver.VIPS_RESOURCE,
                    netscaler_driver.VIP_RESOURCE,
                    expectedvip)
                #Finally, assert that the vip object is now ACTIVE
                self.mock_update_status_obj.assert_called_once_with(
                    mock.ANY,
                    loadbalancer_db.Vip,
                    expectedvip['id'],
                    constants.ACTIVE)

    def test_create_vip_without_connection(self):
        with contextlib.nested(
            self.subnet(),
            mock.patch.object(self.driver.plugin._core_plugin, 'get_subnet')
        ) as (subnet, mock_get_subnet):
            mock_get_subnet.return_value = subnet['subnet']
            with self.pool(provider=LBAAS_PROVIDER_NAME) as pool:
                testvip = self._build_testvip_contents(subnet['subnet'],
                                                       pool['pool'])
                expectedvip = self._build_expectedvip_contents(
                    testvip,
                    subnet['subnet'])
                errorcode = ncc_client.NCCException.CONNECTION_ERROR
                self.create_resource_mock.side_effect = (
                    ncc_client.NCCException(errorcode))
                # mock the plugin's update_status()
                self._mock_update_status()
                # reset the create_resource() mock
                self.create_resource_mock.reset_mock()
                # execute the method under test.
                self.driver.create_vip(self.context, testvip)
                # First, assert that update_resource was called once
                # with expected params.
                self.create_resource_mock.assert_called_once_with(
                    None,
                    netscaler_driver.VIPS_RESOURCE,
                    netscaler_driver.VIP_RESOURCE,
                    expectedvip)
                #Finally, assert that the vip object is in ERROR state
                self.mock_update_status_obj.assert_called_once_with(
                    mock.ANY,
                    loadbalancer_db.Vip,
                    testvip['id'],
                    constants.ERROR)

    def test_update_vip(self):
        with contextlib.nested(
            self.subnet(),
            mock.patch.object(self.driver.plugin._core_plugin, 'get_subnet')
        ) as (subnet, mock_get_subnet):
            mock_get_subnet.return_value = subnet['subnet']
            with self.pool(provider=LBAAS_PROVIDER_NAME) as pool:
                with self.vip(pool=pool, subnet=subnet) as vip:
                    updated_vip = self._build_updated_testvip_contents(
                        vip['vip'],
                        subnet['subnet'],
                        pool['pool'])
                    expectedvip = self._build_updated_expectedvip_contents(
                        updated_vip,
                        subnet['subnet'],
                        pool['pool'])
                    # mock the plugin's update_status()
                    self._mock_update_status()
                    # reset the update_resource() mock
                    self.update_resource_mock.reset_mock()
                    # execute the method under test
                    self.driver.update_vip(self.context, updated_vip,
                                           updated_vip)
                    vip_resource_path = "%s/%s" % (
                        (netscaler_driver.VIPS_RESOURCE,
                         vip['vip']['id']))
                    # First, assert that update_resource was called once
                    # with expected params.
                    (self.update_resource_mock
                         .assert_called_once_with(
                             None,
                             vip_resource_path,
                             netscaler_driver.VIP_RESOURCE,
                             expectedvip))
                    #Finally, assert that the vip object is now ACTIVE
                    self.mock_update_status_obj.assert_called_once_with(
                        mock.ANY,
                        loadbalancer_db.Vip,
                        vip['vip']['id'],
                        constants.ACTIVE)

    def test_delete_vip(self):
        with contextlib.nested(
            self.subnet(),
            mock.patch.object(self.driver.plugin._core_plugin, 'get_subnet')
        ) as (subnet, mock_get_subnet):
            mock_get_subnet.return_value = subnet['subnet']
            with self.pool(provider=LBAAS_PROVIDER_NAME) as pool:
                with contextlib.nested(
                    self.vip(pool=pool, subnet=subnet),
                    mock.patch.object(self.driver.plugin, '_delete_db_vip')
                ) as (vip, mock_delete_db_vip):
                    mock_delete_db_vip.return_value = None
                    #reset the remove_resource() mock
                    self.remove_resource_mock.reset_mock()
                    # execute the method under test
                    self.driver.delete_vip(self.context, vip['vip'])
                    vip_resource_path = "%s/%s" % (
                                        (netscaler_driver.VIPS_RESOURCE,
                                         vip['vip']['id']))
                    # Assert that remove_resource() was called once
                    # with expected params.
                    (self.remove_resource_mock
                         .assert_called_once_with(None, vip_resource_path))

    def test_create_pool(self):
        with contextlib.nested(
            self.subnet(),
            mock.patch.object(self.driver.plugin._core_plugin, 'get_subnet'),
            mock.patch.object(self.driver.plugin._core_plugin, 'get_ports'),
            mock.patch.object(self.driver.plugin._core_plugin, 'create_port')
        ) as (subnet, mock_get_subnet, mock_get_ports, mock_create_port):
            mock_get_subnet.return_value = subnet['subnet']
            mock_get_ports.return_value = None
            mock_create_port.return_value = TESTPOOL_SNAT_PORT
            testpool = self._build_testpool_contents(subnet['subnet'])
            expectedpool = self._build_expectedpool_contents(testpool,
                                                             subnet['subnet'])
            #reset the create_resource() mock
            self.create_resource_mock.reset_mock()
            # mock the plugin's update_status()
            self._mock_update_status()
            # execute the method under test
            self.driver.create_pool(self.context, testpool)
            # First, assert that create_resource was called once
            # with expected params.
            (self.create_resource_mock
                 .assert_called_once_with(None,
                                          netscaler_driver.POOLS_RESOURCE,
                                          netscaler_driver.POOL_RESOURCE,
                                          expectedpool))
            #Finally, assert that the pool object is now ACTIVE
            self.mock_update_status_obj.assert_called_once_with(
                mock.ANY,
                loadbalancer_db.Pool,
                expectedpool['id'],
                constants.ACTIVE)

    def test_create_pool_with_error(self):
        with contextlib.nested(
            self.subnet(),
            mock.patch.object(self.driver.plugin._core_plugin, 'get_subnet'),
            mock.patch.object(self.driver.plugin._core_plugin, 'get_ports'),
            mock.patch.object(self.driver.plugin._core_plugin, 'create_port')
        ) as (subnet, mock_get_subnet, mock_get_ports, mock_create_port):
            mock_get_subnet.return_value = subnet['subnet']
            mock_get_ports.return_value = None
            mock_create_port.return_value = TESTPOOL_SNAT_PORT
            errorcode = ncc_client.NCCException.CONNECTION_ERROR
            self.create_resource_mock.side_effect = (ncc_client