# 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.

"""
test_router
----------------------------------

Functional tests for router methods.
"""

import ipaddress

from openstack import exceptions
from openstack.tests.functional import base


EXPECTED_TOPLEVEL_FIELDS = (
    'id',
    'name',
    'is_admin_state_up',
    'external_gateway_info',
    'project_id',
    'routes',
    'status',
)

EXPECTED_GW_INFO_FIELDS = ('network_id', 'enable_snat', 'external_fixed_ips')


class TestRouter(base.BaseFunctionalTest):
    def setUp(self):
        super().setUp()
        if not self.operator_cloud:
            self.skipTest("Operator cloud required for this test")
        if not self.operator_cloud.has_service('network'):
            self.skipTest('Network service not supported by cloud')

        self.router_prefix = self.getUniqueString('router')
        self.network_prefix = self.getUniqueString('network')
        self.subnet_prefix = self.getUniqueString('subnet')

        # NOTE(Shrews): Order matters!
        self.addCleanup(self._cleanup_networks)
        self.addCleanup(self._cleanup_subnets)
        self.addCleanup(self._cleanup_routers)

    def _cleanup_routers(self):
        exception_list = list()
        for router in self.operator_cloud.list_routers():
            if router['name'].startswith(self.router_prefix):
                try:
                    self.operator_cloud.delete_router(router['name'])
                except Exception as e:
                    exception_list.append(str(e))
                    continue

        if exception_list:
            raise exceptions.SDKException('\n'.join(exception_list))

    def _cleanup_networks(self):
        exception_list = list()
        for network in self.operator_cloud.list_networks():
            if network['name'].startswith(self.network_prefix):
                try:
                    self.operator_cloud.delete_network(network['name'])
                except Exception as e:
                    exception_list.append(str(e))
                    continue

        if exception_list:
            raise exceptions.SDKException('\n'.join(exception_list))

    def _cleanup_subnets(self):
        exception_list = list()
        for subnet in self.operator_cloud.list_subnets():
            if subnet['name'].startswith(self.subnet_prefix):
                try:
                    self.operator_cloud.delete_subnet(subnet['id'])
                except Exception as e:
                    exception_list.append(str(e))
                    continue

        if exception_list:
            raise exceptions.SDKException('\n'.join(exception_list))

    def test_create_router_basic(self):
        net1_name = self.network_prefix + '_net1'
        net1 = self.operator_cloud.create_network(
            name=net1_name, external=True
        )

        router_name = self.router_prefix + '_create_basic'
        router = self.operator_cloud.create_router(
            name=router_name,
            admin_state_up=True,
            ext_gateway_net_id=net1['id'],
        )

        for field in EXPECTED_TOPLEVEL_FIELDS:
            self.assertIn(field, router)

        ext_gw_info = router['external_gateway_info']
        for field in EXPECTED_GW_INFO_FIELDS:
            self.assertIn(field, ext_gw_info)

        self.assertEqual(router_name, router['name'])
        self.assertEqual('ACTIVE', router['status'])
        self.assertEqual(net1['id'], ext_gw_info['network_id'])
        self.assertTrue(ext_gw_info['enable_snat'])

    def test_create_router_project(self):
        project = self.operator_cloud.get_project('demo')
        self.assertIsNotNone(project)
        proj_id = project['id']
        net1_name = self.network_prefix + '_net1'
        net1 = self.operator_cloud.create_network(
            name=net1_name, external=True, project_id=proj_id
        )

        router_name = self.router_prefix + '_create_project'
        router = self.operator_cloud.create_router(
            name=router_name,
            admin_state_up=True,
            ext_gateway_net_id=net1['id'],
            project_id=proj_id,
        )

        for field in EXPECTED_TOPLEVEL_FIELDS:
            self.assertIn(field, router)

        ext_gw_info = router['external_gateway_info']
        for field in EXPECTED_GW_INFO_FIELDS:
            self.assertIn(field, ext_gw_info)

        self.assertEqual(router_name, router['name'])
        self.assertEqual('ACTIVE', router['status'])
        self.assertEqual(proj_id, router['tenant_id'])
        self.assertEqual(net1['id'], ext_gw_info['network_id'])
        self.assertTrue(ext_gw_info['enable_snat'])

    def _create_and_verify_advanced_router(
        self, external_cidr, external_gateway_ip=None
    ):
        # external_cidr must be passed in as unicode (u'')
        # NOTE(Shrews): The arguments are needed because these tests
        # will run in parallel and we want to make sure that each test
        # is using different resources to prevent race conditions.
        net1_name = self.network_prefix + '_net1'
        sub1_name = self.subnet_prefix + '_sub1'
        net1 = self.operator_cloud.create_network(
            name=net1_name, external=True
        )
        sub1 = self.operator_cloud.create_subnet(
            net1['id'],
            external_cidr,
            subnet_name=sub1_name,
            gateway_ip=external_gateway_ip,
        )

        ip_net = ipaddress.IPv4Network(external_cidr)
        last_ip = str(list(ip_net.hosts())[-1])

        router_name = self.router_prefix + '_create_advanced'
        router = self.operator_cloud.create_router(
            name=router_name,
            admin_state_up=False,
            ext_gateway_net_id=net1['id'],
            enable_snat=False,
            ext_fixed_ips=[{'subnet_id': sub1['id'], 'ip_address': last_ip}],
        )

        for field in EXPECTED_TOPLEVEL_FIELDS:
            self.assertIn(field, router)

        ext_gw_info = router['external_gateway_info']
        for field in EXPECTED_GW_INFO_FIELDS:
            self.assertIn(field, ext_gw_info)

        self.assertEqual(router_name, router['name'])
        self.assertEqual('ACTIVE', router['status'])
        self.assertFalse(router['admin_state_up'])

        self.assertEqual(1, len(ext_gw_info['external_fixed_ips']))
        self.assertEqual(
            sub1['id'], ext_gw_info['external_fixed_ips'][0]['subnet_id']
        )
        self.assertEqual(
            last_ip, ext_gw_info['external_fixed_ips'][0]['ip_address']
        )

        return router

    def test_create_router_advanced(self):
        self._create_and_verify_advanced_router(external_cidr='10.2.2.0/24')

    def test_add_remove_router_interface(self):
        router = self._create_and_verify_advanced_router(
            external_cidr='10.3.3.0/24'
        )
        net_name = self.network_prefix + '_intnet1'
        sub_name = self.subnet_prefix + '_intsub1'
        net = self.operator_cloud.create_network(name=net_name)
        sub = self.operator_cloud.create_subnet(
            net['id'],
            '10.4.4.0/24',
            subnet_name=sub_name,
            gateway_ip='10.4.4.1',
        )

        iface = self.operator_cloud.add_router_interface(
            router, subnet_id=sub['id']
        )
        self.assertIsNone(
            self.operator_cloud.remove_router_interface(
                router, subnet_id=sub['id']
            )
        )

        # Test return values *after* the interface is detached so the
        # resources we've created can be cleaned up if these asserts fail.
        self.assertIsNotNone(iface)
        for key in ('id', 'subnet_id', 'port_id', 'tenant_id'):
            self.assertIn(key, iface)
        self.assertEqual(router['id'], iface['id'])
        self.assertEqual(sub['id'], iface['subnet_id'])

    def test_list_router_interfaces(self):
        router = self._create_and_verify_advanced_router(
            external_cidr='10.5.5.0/24'
        )
        net_name = self.network_prefix + '_intnet1'
        sub_name = self.subnet_prefix + '_intsub1'
        net = self.operator_cloud.create_network(name=net_name)
        sub = self.operator_cloud.create_subnet(
            net['id'],
            '10.6.6.0/24',
            subnet_name=sub_name,
            gateway_ip='10.6.6.1',
        )

        iface = self.operator_cloud.add_router_interface(
            router, subnet_id=sub['id']
        )
        all_ifaces = self.operator_cloud.list_router_interfaces(router)
        int_ifaces = self.operator_cloud.list_router_interfaces(
            router, interface_type='internal'
        )
        ext_ifaces = self.operator_cloud.list_router_interfaces(
            router, interface_type='external'
        )
        self.assertIsNone(
            self.operator_cloud.remove_router_interface(
                router, subnet_id=sub['id']
            )
        )

        # Test return values *after* the interface is detached so the
        # resources we've created can be cleaned up if these asserts fail.
        self.assertIsNotNone(iface)
        self.assertEqual(2, len(all_ifaces))
        self.assertEqual(1, len(int_ifaces))
        self.assertEqual(1, len(ext_ifaces))

        ext_fixed_ips = router['external_gateway_info']['external_fixed_ips']
        self.assertEqual(
            ext_fixed_ips[0]['subnet_id'],
            ext_ifaces[0]['fixed_ips'][0]['subnet_id'],
        )
        self.assertEqual(sub['id'], int_ifaces[0]['fixed_ips'][0]['subnet_id'])

    def test_update_router_name(self):
        router = self._create_and_verify_advanced_router(
            external_cidr='10.7.7.0/24'
        )

        new_name = self.router_prefix + '_update_name'
        updated = self.operator_cloud.update_router(
            router['id'], name=new_name
        )
        self.assertIsNotNone(updated)

        for field in EXPECTED_TOPLEVEL_FIELDS:
            self.assertIn(field, updated)

        # Name is the only change we expect
        self.assertEqual(new_name, updated['name'])

        # Validate nothing else changed
        self.assertEqual(router['status'], updated['status'])
        self.assertEqual(router['admin_state_up'], updated['admin_state_up'])
        self.assertEqual(
            router['external_gateway_info'], updated['external_gateway_info']
        )

    def test_update_router_routes(self):
        router = self._create_and_verify_advanced_router(
            external_cidr='10.7.7.0/24'
        )

        routes = [{"destination": "10.7.7.0/24", "nexthop": "10.7.7.99"}]

        updated = self.operator_cloud.update_router(
            router['id'], routes=routes
        )
        self.assertIsNotNone(updated)

        for field in EXPECTED_TOPLEVEL_FIELDS:
            self.assertIn(field, updated)

        # Name is the only change we expect
        self.assertEqual(routes, updated['routes'])

        # Validate nothing else changed
        self.assertEqual(router['status'], updated['status'])
        self.assertEqual(router['admin_state_up'], updated['admin_state_up'])
        self.assertEqual(
            router['external_gateway_info'], updated['external_gateway_info']
        )

    def test_update_router_admin_state(self):
        router = self._create_and_verify_advanced_router(
            external_cidr='10.8.8.0/24'
        )

        updated = self.operator_cloud.update_router(
            router['id'], admin_state_up=True
        )
        self.assertIsNotNone(updated)

        for field in EXPECTED_TOPLEVEL_FIELDS:
            self.assertIn(field, updated)

        # admin_state_up is the only change we expect
        self.assertTrue(updated['admin_state_up'])
        self.assertNotEqual(
            router['admin_state_up'], updated['admin_state_up']
        )

        # Validate nothing else changed
        self.assertEqual(router['status'], updated['status'])
        self.assertEqual(router['name'], updated['name'])
        self.assertEqual(
            router['external_gateway_info'], updated['external_gateway_info']
        )

    def test_update_router_ext_gw_info(self):
        router = self._create_and_verify_advanced_router(
            external_cidr='10.9.9.0/24'
        )

        # create a new subnet
        existing_net_id = router['external_gateway_info']['network_id']
        sub_name = self.subnet_prefix + '_update'
        sub = self.operator_cloud.create_subnet(
            existing_net_id,
            '10.10.10.0/24',
            subnet_name=sub_name,
            gateway_ip='10.10.10.1',
        )

        updated = self.operator_cloud.update_router(
            router['id'],
            ext_gateway_net_id=existing_net_id,
            ext_fixed_ips=[
                {'subnet_id': sub['id'], 'ip_address': '10.10.10.77'}
            ],
        )
        self.assertIsNotNone(updated)

        for field in EXPECTED_TOPLEVEL_FIELDS:
            self.assertIn(field, updated)

        # external_gateway_info is the only change we expect
        ext_gw_info = updated['external_gateway_info']
        self.assertEqual(1, len(ext_gw_info['external_fixed_ips']))
        self.assertEqual(
            sub['id'], ext_gw_info['external_fixed_ips'][0]['subnet_id']
        )
        self.assertEqual(
            '10.10.10.77', ext_gw_info['external_fixed_ips'][0]['ip_address']
        )

        # Validate nothing else changed
        self.assertEqual(router['status'], updated['status'])
        self.assertEqual(router['name'], updated['name'])
        self.assertEqual(router['admin_state_up'], updated['admin_state_up'])
