# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
#    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.

from tempest.common import utils
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
from tempest.lib import exceptions

from neutron_tempest_plugin.api import base
from neutron_tempest_plugin import config

CONF = config.CONF


class PortForwardingTestJSON(base.BaseNetworkTest):

    required_extensions = ['router', 'floating-ip-port-forwarding']

    @classmethod
    def resource_setup(cls):
        super(PortForwardingTestJSON, cls).resource_setup()
        cls.ext_net_id = CONF.network.public_network_id

        # Create network, subnet, router and add interface
        cls.network = cls.create_network()
        cls.subnet = cls.create_subnet(cls.network)
        cls.router = cls.create_router(data_utils.rand_name('router'),
                                       external_network_id=cls.ext_net_id)
        cls.create_router_interface(cls.router['id'], cls.subnet['id'])

    @decorators.idempotent_id('829a446e-46bc-41ce-b442-6e428aeb3c19')
    def test_port_forwarding_life_cycle(self):
        fip = self.create_floatingip()
        port = self.create_port(self.network)
        # Create port forwarding for one TCP port
        created_pf = self.create_port_forwarding(
            fip['id'],
            internal_port_id=port['id'],
            internal_ip_address=port['fixed_ips'][0]['ip_address'],
            internal_port=1111, external_port=2222, protocol="tcp")
        self.assertEqual(1111, created_pf['internal_port'])
        self.assertEqual(2222, created_pf['external_port'])
        self.assertEqual('tcp', created_pf['protocol'])
        self.assertEqual(port['fixed_ips'][0]['ip_address'],
                         created_pf['internal_ip_address'])

        # Show created port forwarding
        body = self.client.get_port_forwarding(
            fip['id'], created_pf['id'])
        pf = body['port_forwarding']
        self.assertEqual(1111, pf['internal_port'])
        self.assertEqual(2222, pf['external_port'])
        self.assertEqual('tcp', pf['protocol'])
        self.assertEqual(port['fixed_ips'][0]['ip_address'],
                         pf['internal_ip_address'])

        # Update port forwarding
        body = self.client.update_port_forwarding(
            fip['id'], pf['id'], internal_port=3333)
        pf = body['port_forwarding']
        self.assertEqual(3333, pf['internal_port'])
        self.assertEqual(2222, pf['external_port'])
        self.assertEqual('tcp', pf['protocol'])
        self.assertEqual(port['fixed_ips'][0]['ip_address'],
                         pf['internal_ip_address'])

        # Now lets try to remove Floating IP with existing port forwarding,
        # this should fails
        self.assertRaises(exceptions.Conflict,
                          self.delete_floatingip, fip)

        # Delete port forwarding
        self.client.delete_port_forwarding(fip['id'], pf['id'])
        self.assertRaises(exceptions.NotFound,
                          self.client.get_port_forwarding,
                          fip['id'], pf['id'])

        # Now Floating IP should be deleted properly
        self.delete_floatingip(fip)
        self.assertRaises(exceptions.NotFound,
                          self.client.get_floatingip, fip['id'])

    @decorators.idempotent_id('aa842070-39ef-4b09-9df9-e723934f96f8')
    @utils.requires_ext(extension="expose-port-forwarding-in-fip",
                        service="network")
    def test_port_forwarding_info_in_fip_details(self):
        fip = self.create_floatingip()
        port = self.create_port(self.network)

        # Ensure that FIP don't have information about any port forwarding yet
        fip = self.client.show_floatingip(fip['id'])['floatingip']
        self.assertEqual(0, len(fip['port_forwardings']))

        # Now create port forwarding and ensure that it is visible in FIP's
        # details
        pf = self.create_port_forwarding(
            fip['id'],
            internal_port_id=port['id'],
            internal_ip_address=port['fixed_ips'][0]['ip_address'],
            internal_port=1111, external_port=2222, protocol="tcp")
        fip = self.client.show_floatingip(fip['id'])['floatingip']
        self.assertEqual(1, len(fip['port_forwardings']))
        self.assertEqual(1111, fip['port_forwardings'][0]['internal_port'])
        self.assertEqual(2222, fip['port_forwardings'][0]['external_port'])
        self.assertEqual('tcp', fip['port_forwardings'][0]['protocol'])
        self.assertEqual(port['fixed_ips'][0]['ip_address'],
                         fip['port_forwardings'][0]['internal_ip_address'])

        # Delete port forwarding and ensure that it's not in FIP's details
        # anymore
        self.client.delete_port_forwarding(fip['id'], pf['id'])
        fip = self.client.show_floatingip(fip['id'])['floatingip']
        self.assertEqual(0, len(fip['port_forwardings']))

    def _verify_created_pfs(self, fip_id, created_pfs):
        # Check that all PFs are visible in Floating IP details
        fip = self.client.show_floatingip(fip_id)['floatingip']
        self.assertEqual(len(created_pfs), len(fip['port_forwardings']))
        detail_enabled = utils.is_extension_enabled(
            'floating-ip-port-forwarding-detail', service='network')
        for pf in created_pfs:
            expected_pf = {
                'external_port': pf['external_port'],
                'internal_port': pf['internal_port'],
                'protocol': pf['protocol'],
                'internal_ip_address': pf['internal_ip_address']}
            if detail_enabled:
                expected_pf['id'] = pf['id']
                expected_pf['internal_port_id'] = pf['internal_port_id']
            self.assertIn(expected_pf, fip['port_forwardings'])

        # Test list of port forwardings
        port_forwardings = self.client.list_port_forwardings(
            fip['id'])['port_forwardings']
        self.assertEqual(len(created_pfs), len(port_forwardings))
        for pf in created_pfs:
            expected_pf = pf.copy()
            expected_pf.pop('client')
            expected_pf.pop('floatingip_id')
            self.assertIn(expected_pf, port_forwardings)

    def _create_and_validate_pf(self, fip_id, internal_port_id,
                                internal_ip_address, internal_port,
                                external_port, protocol):
        created_pf = self.create_port_forwarding(
            fip_id,
            internal_port_id=internal_port_id,
            internal_ip_address=internal_ip_address,
            internal_port=internal_port,
            external_port=external_port,
            protocol=protocol)
        self.assertEqual(internal_port, created_pf['internal_port'])
        self.assertEqual(external_port, created_pf['external_port'])
        self.assertEqual(protocol, created_pf['protocol'])
        self.assertEqual(internal_ip_address,
                         created_pf['internal_ip_address'])
        return created_pf

    @decorators.idempotent_id('8202cded-7e82-4420-9585-c091105404f6')
    def test_associate_2_port_forwardings_to_floating_ip(self):
        fip = self.create_floatingip()
        forwardings_data = [(1111, 2222), (3333, 4444)]
        created_pfs = []
        for data in forwardings_data:
            internal_port = data[0]
            external_port = data[1]
            port = self.create_port(self.network)
            created_pf = self._create_and_validate_pf(
                fip_id=fip['id'],
                internal_port_id=port['id'],
                internal_ip_address=port['fixed_ips'][0]['ip_address'],
                internal_port=internal_port, external_port=external_port,
                protocol="tcp")
            created_pfs.append(created_pf)
        self._verify_created_pfs(fip['id'], created_pfs)

    @decorators.idempotent_id('a7e6cc48-8a9b-49be-82fb-cef6f5c29381')
    def test_associate_port_forwarding_to_2_fixed_ips(self):
        fip = self.create_floatingip()
        port = self.create_port(self.network)
        internal_subnet_id = port['fixed_ips'][0]['subnet_id']
        # Add a second fixed_ip address to port (same subnet)
        port['fixed_ips'].append({'subnet_id': internal_subnet_id})
        port = self.update_port(port, fixed_ips=port['fixed_ips'])
        internal_ip_address1 = port['fixed_ips'][0]['ip_address']
        internal_ip_address2 = port['fixed_ips'][1]['ip_address']
        forwardings_data = [(4001, internal_ip_address1),
                            (4002, internal_ip_address2)]
        created_pfs = []
        for data in forwardings_data:
            external_port = data[0]
            internal_ip_address = data[1]
            created_pf = self._create_and_validate_pf(
                fip_id=fip['id'],
                internal_port_id=port['id'],
                internal_ip_address=internal_ip_address,
                internal_port=123, external_port=external_port,
                protocol="tcp")
            created_pfs.append(created_pf)
        self._verify_created_pfs(fip['id'], created_pfs)

    @decorators.idempotent_id('6a34e811-66d1-4f63-aa4d-9013f15deb62')
    def test_associate_port_forwarding_to_used_floating_ip(self):
        port_for_fip = self.create_port(self.network)
        fip = self.create_floatingip(port=port_for_fip)
        port = self.create_port(self.network)
        self.assertRaises(
            exceptions.Conflict,
            self.create_port_forwarding,
            fip['id'],
            internal_port_id=port['id'],
            internal_ip_address=port['fixed_ips'][0]['ip_address'],
            internal_port=1111, external_port=2222,
            protocol="tcp")

    @decorators.idempotent_id('4ca72d40-93e4-485f-a876-76caf33c1fe6')
    def test_associate_port_forwarding_to_port_with_fip(self):
        port = self.create_port(self.network)
        self.create_floatingip(port=port)
        fip_for_pf = self.create_floatingip()
        self.assertRaises(
            exceptions.Conflict,
            self.create_port_forwarding,
            fip_for_pf['id'],
            internal_port_id=port['id'],
            internal_ip_address=port['fixed_ips'][0]['ip_address'],
            internal_port=1111, external_port=2222,
            protocol="tcp")
