# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 Radware LTD.
#
#    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.
#
# @author: Avishay Balderman, Radware

import Queue
import re

import contextlib
import mock

from neutron import context
from neutron.extensions import loadbalancer
from neutron import manager
from neutron.openstack.common import jsonutils as json
from neutron.plugins.common import constants
from neutron.services.loadbalancer.drivers.radware import driver
from neutron.services.loadbalancer.drivers.radware import exceptions as r_exc
from neutron.tests.unit.db.loadbalancer import test_db_loadbalancer

GET_200 = ('/api/workflow/', '/api/service/', '/api/workflowTemplate')


class QueueMock(Queue.Queue):
    def __init__(self, completion_handler):
        self.completion_handler = completion_handler
        super(QueueMock, self).__init__()

    def put_nowait(self, oper):
        self.completion_handler(oper)


def rest_call_function_mock(action, resource, data, headers, binary=False):
    if rest_call_function_mock.RESPOND_WITH_ERROR:
        return 400, 'error_status', 'error_description', None

    if action == 'GET':
        return _get_handler(resource)
    elif action == 'DELETE':
        return _delete_handler(resource)
    elif action == 'POST':
        return _post_handler(resource, binary)
    else:
        return 0, None, None, None


def _get_handler(resource):
    if resource == GET_200[2]:
        if rest_call_function_mock.TEMPLATES_MISSING:
            data = json.loads('[]')
        else:
            data = json.loads(
                '[{"name":"openstack_l2_l3"},{"name":"openstack_l4"}]'
            )
        return 200, '', '', data

    if resource in GET_200:
        return 200, '', '', ''
    else:
        data = json.loads('{"complete":"True", "success": "True"}')
        return 202, '', '', data


def _delete_handler(resource):
    return 404, '', '', {'message': 'Not Found'}


def _post_handler(resource, binary):
    if re.search(r'/api/workflow/.+/action/.+', resource):
        data = json.loads('{"uri":"some_uri"}')
        return 202, '', '', data
    elif re.search(r'/api/service\?name=.+', resource):
        data = json.loads('{"links":{"actions":{"provision":"someuri"}}}')
        return 201, '', '', data
    elif binary:
        return 201, '', '', ''
    else:
        return 202, '', '', ''

RADWARE_PROVIDER = ('LOADBALANCER:radware:neutron.services.'
                    'loadbalancer.drivers.radware.driver.'
                    'LoadBalancerDriver:default')


class TestLoadBalancerPluginBase(
    test_db_loadbalancer.LoadBalancerPluginDbTestCase):

    def setUp(self):
        super(TestLoadBalancerPluginBase, self).setUp(
            lbaas_provider=RADWARE_PROVIDER)

        loaded_plugins = manager.NeutronManager().get_service_plugins()
        self.plugin_instance = loaded_plugins[constants.LOADBALANCER]


class TestLoadBalancerPlugin(TestLoadBalancerPluginBase):
    def setUp(self):
        super(TestLoadBalancerPlugin, self).setUp()

        rest_call_function_mock.__dict__.update(
            {'RESPOND_WITH_ERROR': False})
        rest_call_function_mock.__dict__.update(
            {'TEMPLATES_MISSING': False})

        self.operation_completer_start_mock = mock.Mock(
            return_value=None)
        self.operation_completer_join_mock = mock.Mock(
            return_value=None)
        self.driver_rest_call_mock = mock.Mock(
            side_effect=rest_call_function_mock)

        radware_driver = self.plugin_instance.drivers['radware']
        radware_driver.completion_handler.start = (
            self.operation_completer_start_mock)
        radware_driver.completion_handler.join = (
            self.operation_completer_join_mock)
        radware_driver.rest_client.call = self.driver_rest_call_mock
        radware_driver.completion_handler.rest_client.call = (
            self.driver_rest_call_mock)

        radware_driver.queue = QueueMock(
            radware_driver.completion_handler.handle_operation_completion)

        self.addCleanup(radware_driver.completion_handler.join)

    def test_verify_workflow_templates(self):
        """Test the rest call failure handling by Exception raising."""
        rest_call_function_mock.__dict__.update(
            {'TEMPLATES_MISSING': True})

        self.assertRaises(r_exc.WorkflowMissing,
                          self.plugin_instance.drivers['radware'].
                          _verify_workflow_templates)

    def test_create_vip_failure(self):
        """Test the rest call failure handling by Exception raising."""
        with self.network(do_delete=False) as network:
            with self.subnet(network=network, do_delete=False) as subnet:
                with self.pool(no_delete=True,
                               provider='radware',
                               subnet_id=subnet['subnet']['id']) as pool:
                    vip_data = {
                        'name': 'vip1',
                        'subnet_id': subnet['subnet']['id'],
                        'pool_id': pool['pool']['id'],
                        'description': '',
                        'protocol_port': 80,
                        'protocol': 'HTTP',
                        'connection_limit': -1,
                        'admin_state_up': True,
                        'status': constants.PENDING_CREATE,
                        'tenant_id': self._tenant_id,
                        'session_persistence': ''
                    }

                    rest_call_function_mock.__dict__.update(
                        {'RESPOND_WITH_ERROR': True})

                    self.assertRaises(r_exc.RESTRequestFailure,
                                      self.plugin_instance.create_vip,
                                      context.get_admin_context(),
                                      {'vip': vip_data})

    def test_create_vip(self):
        with self.subnet() as subnet:
            with self.pool(provider='radware',
                           subnet_id=subnet['subnet']['id']) as pool:
                vip_data = {
                    'name': 'vip1',
                    'subnet_id': subnet['subnet']['id'],
                    'pool_id': pool['pool']['id'],
                    'description': '',
                    'protocol_port': 80,
                    'protocol': 'HTTP',
                    'connection_limit': -1,
                    'admin_state_up': True,
                    'status': constants.PENDING_CREATE,
                    'tenant_id': self._tenant_id,
                    'session_persistence': ''
                }

                vip = self.plugin_instance.create_vip(
                    context.get_admin_context(), {'vip': vip_data})

                # Test creation REST calls
                calls = [
                    mock.call('GET', u'/api/service/srv_' +
                              subnet['subnet']['network_id'], None, None),
                    mock.call('POST', u'/api/service?name=srv_' +
                              subnet['subnet']['network_id'] + '&tenant=' +
                              vip['tenant_id'], mock.ANY,
                              driver.CREATE_SERVICE_HEADER),
                    mock.call('GET', u'/api/workflow/l2_l3_' +
                              subnet['subnet']['network_id'], None, None),
                    mock.call('POST', '/api/workflow/l2_l3_' +
                              subnet['subnet']['network_id'] +
                              '/action/setup_l2_l3',
                              mock.ANY, driver.TEMPLATE_HEADER),
                    mock.call('POST', 'someuri',
                              None, driver.PROVISION_HEADER),


                    mock.call('POST', '/api/workflowTemplate/' +
                              'openstack_l4' +
                              '?name=' + pool['pool']['id'],
                              mock.ANY,
                              driver.TEMPLATE_HEADER),
                    mock.call('POST', '/api/workflowTemplate/' +
                              'openstack_l2_l3' +
                              '?name=l2_l3_' + subnet['subnet']['network_id'],
                              mock.ANY,
                              driver.TEMPLATE_HEADER),

                    mock.call('POST', '/api/workflow/' + pool['pool']['id'] +
                              '/action/BaseCreate',
                              mock.ANY, driver.TEMPLATE_HEADER),
                    mock.call('GET', '/api/workflow/' +
                              pool['pool']['id'], None, None)
                ]
                self.driver_rest_call_mock.assert_has_calls(calls,
                                                            any_order=True)

                #Test DB
                new_vip = self.plugin_instance.get_vip(
                    context.get_admin_context(),
                    vip['id']
                )
                self.assertEqual(new_vip['status'], constants.ACTIVE)

                # Delete VIP
                self.plugin_instance.delete_vip(
                    context.get_admin_context(), vip['id'])

                # Test deletion REST calls
                calls = [
                    mock.call('DELETE', u'/api/workflow/' + pool['pool']['id'],
                              None, None)
                ]
                self.driver_rest_call_mock.assert_has_calls(
                    calls, any_order=True)

    def test_create_vip_2_leg(self):
        """Test creation of a VIP where Alteon VIP and PIP are different."""

        with self.subnet(cidr='10.0.0.0/24') as subnet:
            with self.subnet(cidr='10.0.1.0/24') as pool_sub:
                with self.pool(provider='radware',
                               subnet_id=pool_sub['subnet']['id']) as pool:
                    vip_data = {
                        'name': 'vip1',
                        'subnet_id': subnet['subnet']['id'],
                        'pool_id': pool['pool']['id'],
                        'description': '',
                        'protocol_port': 80,
                        'protocol': 'HTTP',
                        'connection_limit': -1,
                        'admin_state_up': True,
                        'status': constants.PENDING_CREATE,
                        'tenant_id': self._tenant_id,
                        'session_persistence': ''
                    }

                    vip = self.plugin_instance.create_vip(
                        context.get_admin_context(), {'vip': vip_data})
                    name_suffix = '%s_%s' % (subnet['subnet']['network_id'],
                                             pool_sub['subnet']['network_id'])
                    # Test creation REST calls
                    calls = [
                        mock.call('GET', '/api/workflowTemplate', None, None),
                        mock.call('GET', '/api/service/srv_' + name_suffix,
                                  None, None),
                        mock.call('POST', '/api/service?name=srv_' +
                                  name_suffix + '&tenant=' + vip['tenant_id'],
                                  mock.ANY, driver.CREATE_SERVICE_HEADER),
                        mock.call('POST', 'someuri',
                                  None, driver.PROVISION_HEADER),
                        mock.call('GET', '/api/workflow/l2_l3_' + name_suffix,
                                  None, None),
                        mock.call('POST', '/api/workflowTemplate/' +
                                  'openstack_l2_l3' +
                                  '?name=l2_l3_' + name_suffix,
                                  mock.ANY,
                                  driver.TEMPLATE_HEADER),
                        mock.call('POST', '/api/workflow/l2_l3_' +
                                  name_suffix + '/action/setup_l2_l3',
                                  mock.ANY, driver.TEMPLATE_HEADER),
                        mock.call('GET', '/api/workflow/' +
                                  pool['pool']['id'], None, None),
                        mock.call('POST', '/api/workflowTemplate/' +
                                  'openstack_l4' +
                                  '?name=' + pool['pool']['id'],
                                  mock.ANY,
                                  driver.TEMPLATE_HEADER),
                        mock.call('POST', '/api/workflow/' +
                                  pool['pool']['id'] + '/action/BaseCreate',
                                  mock.ANY, driver.TEMPLATE_HEADER)
                    ]
                    self.driver_rest_call_mock.assert_has_calls(calls)
                    #Test DB
                    new_vip = self.plugin_instance.get_vip(
                        context.get_admin_context(),
                        vip['id']
                    )
                    self.assertEqual(new_vip['status'], constants.ACTIVE)

                    # Test that PIP neutron port was created
                    pip_port_filter = {
                        'name': ['pip_' + vip['id']],
                    }
                    plugin = manager.NeutronManager.get_plugin()
                    num_ports = plugin.get_ports_count(
                        context.get_admin_context(), filters=pip_port_filter)
                    self.assertTrue(num_ports > 0)

                    # Delete VIP
                    self.plugin_instance.delete_vip(
                        context.get_admin_context(), vip['id'])

                    # Test deletion REST calls
                    calls = [
                        mock.call('DELETE', u'/api/workflow/' +
                                  pool['pool']['id'], None, None)
                    ]
                    self.driver_rest_call_mock.assert_has_calls(calls)

    def test_update_vip(self):
        with self.subnet() as subnet:
            with self.pool(provider='radware',
                           no_delete=True,
                           subnet_id=subnet['subnet']['id']) as pool:
                vip_data = {
                    'name': 'vip1',
                    'subnet_id': subnet['subnet']['id'],
                    'pool_id': pool['pool']['id'],
                    'description': '',
                    'protocol_port': 80,
                    'protocol': 'HTTP',
                    'connection_limit': -1,
                    'admin_state_up': True,
                    'status': constants.PENDING_CREATE,
                    'tenant_id': self._tenant_id,
                    'session_persistence': ''
                }

                vip = self.plugin_instance.create_vip(
                    context.get_admin_context(), {'vip': vip_data})

                vip_data['status'] = constants.PENDING_UPDATE
                self.plugin_instance.update_vip(
                    context.get_admin_context(),
                    vip['id'], {'vip': vip_data})

                # Test REST calls
                calls = [
                    mock.call('POST', '/api/workflow/' + pool['pool']['id'] +
                              '/action/BaseCreate',
                              mock.ANY, driver.TEMPLATE_HEADER),
                ]
                self.driver_rest_call_mock.assert_has_calls(
                    calls, any_order=True)

                updated_vip = self.plugin_instance.get_vip(
                    context.get_admin_context(), vip['id'])
                self.assertEqual(updated_vip['status'], constants.ACTIVE)

                # delete VIP
                self.plugin_instance.delete_vip(
                    context.get_admin_context(), vip['id'])

    def test_update_vip_2_leg(self):
        """Test update of a VIP where Alteon VIP and PIP are different."""

        with self.subnet(cidr='10.0.0.0/24') as subnet:
            with self.subnet(cidr='10.0.1.0/24') as pool_subnet:
                with self.pool(provider='radware',
                               subnet_id=pool_subnet['subnet']['id']) as pool:
                    vip_data = {
                        'name': 'vip1',
                        'subnet_id': subnet['subnet']['id'],
                        'pool_id': pool['pool']['id'],
                        'description': '',
                        'protocol_port': 80,
                        'protocol': 'HTTP',
                        'connection_limit': -1,
                        'admin_state_up': True,
                        'status': constants.PENDING_CREATE,
                        'tenant_id': self._tenant_id,
                        'session_persistence': ''
                    }

                    vip = self.plugin_instance.create_vip(
                        context.get_admin_context(), {'vip': vip_data})

                    self.plugin_instance.update_vip(
                        context.get_admin_context(),
                        vip['id'], {'vip': vip_data})

                    # Test REST calls
                    calls = [
                        mock.call('POST', '/api/workflow/' +
                                  pool['pool']['id'] + '/action/BaseCreate',
                                  mock.ANY, driver.TEMPLATE_HEADER),
                    ]
                    self.driver_rest_call_mock.assert_has_calls(calls)

                    updated_vip = self.plugin_instance.get_vip(
                        context.get_admin_context(), vip['id'])
                    self.assertEqual(updated_vip['status'], constants.ACTIVE)

                    # delete VIP
                    self.plugin_instance.delete_vip(
                        context.get_admin_context(), vip['id'])

    def test_delete_vip_failure(self):
        plugin = self.plugin_instance

        with self.network(do_delete=False) as network:
            with self.subnet(network=network, do_delete=False) as subnet:
                with self.pool(no_delete=True,
                               provider='radware',
                               subnet_id=subnet['subnet']['id']) as pool: