# Copyright 2013, Nachi Ueno, NTT MCL, 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 oslo_config import cfg
from oslo_utils import uuidutils
from webob import exc

from neutron.common import constants
from neutron.common import utils
from neutron import context
from neutron.db import extraroute_db
from neutron.extensions import extraroute
from neutron.extensions import l3
from neutron.tests.unit.api.v2 import test_base
from neutron.tests.unit.extensions import test_l3 as test_l3


_uuid = uuidutils.generate_uuid
_get_path = test_base._get_path


class ExtraRouteTestExtensionManager(object):

    def get_resources(self):
        l3.RESOURCE_ATTRIBUTE_MAP['routers'].update(
            extraroute.EXTENDED_ATTRIBUTES_2_0['routers'])
        return l3.L3.get_resources()

    def get_actions(self):
        return []

    def get_request_extensions(self):
        return []


# This plugin class is for tests with plugin that integrates L3.
class TestExtraRouteIntPlugin(test_l3.TestL3NatIntPlugin,
                              extraroute_db.ExtraRoute_db_mixin):
    supported_extension_aliases = ["external-net", "router", "extraroute"]


# A fake l3 service plugin class with extra route capability for
# plugins that delegate away L3 routing functionality
class TestExtraRouteL3NatServicePlugin(test_l3.TestL3NatServicePlugin,
                                       extraroute_db.ExtraRoute_db_mixin):
    supported_extension_aliases = ["router", "extraroute"]


class ExtraRouteDBTestCaseBase(object):
    def _routes_update_prepare(
            self, router_id, subnet_id,
            port_id, routes, skip_add=False, tenant_id=None):
        if not skip_add:
            self._router_interface_action(
                'add', router_id, subnet_id, port_id, tenant_id=None)
        ctxt = context.Context('', tenant_id) if tenant_id else None
        self._update('routers', router_id, {'router': {'routes': routes}},
                     neutron_context=ctxt)
        return self._show('routers', router_id)

    def _routes_update_cleanup(self, port_id, subnet_id, router_id, routes):
        self._update('routers', router_id, {'router': {'routes': routes}})
        self._router_interface_action('remove', router_id, subnet_id, port_id)

    def test_route_update_with_one_route(self):
        routes = [{'destination': '135.207.0.0/16', 'nexthop': '10.0.1.3'}]
        with self.router() as r:
            with self.subnet(cidr='10.0.1.0/24') as s:
                with self.port(subnet=s) as p:
                    body = self._routes_update_prepare(r['router']['id'],
                                                       None, p['port']['id'],
                                                       routes)
                    self.assertEqual(routes, body['router']['routes'])
                    self._routes_update_cleanup(p['port']['id'],
                                                None, r['router']['id'], [])

    def test_route_update_with_external_route(self):
        my_tenant = 'tenant1'
        routes = [{'destination': '135.207.0.0/16', 'nexthop': '10.0.1.3'}]
        with self.subnet(cidr='10.0.1.0/24', tenant_id='notme') as ext_subnet:
            self._set_net_external(ext_subnet['subnet']['network_id'])
            ext_info = {'network_id': ext_subnet['subnet']['network_id']}
            with self.router(
                    external_gateway_info=ext_info, tenant_id=my_tenant) as r:
                body = self._routes_update_prepare(
                    r['router']['id'], None, None, routes, skip_add=True,
                    tenant_id=my_tenant)
                self.assertEqual(routes, body['router']['routes'])

    def test_route_update_with_route_via_another_tenant_subnet(self):
        my_tenant = 'tenant1'
        routes = [{'destination': '135.207.0.0/16', 'nexthop': '10.0.1.3'}]
        with self.subnet(cidr='10.0.1.0/24', tenant_id='notme') as subnet:
            with self.router(tenant_id=my_tenant) as r:
                body = self._routes_update_prepare(
                    r['router']['id'], subnet['subnet']['id'], None, routes,
                    tenant_id=my_tenant)
                self.assertEqual(routes, body['router']['routes'])

    def test_route_clear_routes_with_None(self):
        routes = [{'destination': '135.207.0.0/16',
                   'nexthop': '10.0.1.3'},
                  {'destination': '12.0.0.0/8',
                   'nexthop': '10.0.1.4'},
                  {'destination': '141.212.0.0/16',
                   'nexthop': '10.0.1.5'}]
        with self.router() as r:
            with self.subnet(cidr='10.0.1.0/24') as s:
                with self.port(subnet=s) as p:
                    self._routes_update_prepare(r['router']['id'],
                                                None, p['port']['id'], routes)
                    body = self._update('routers', r['router']['id'],
                                        {'router': {'routes': None}})
                    self.assertEqual([], body['router']['routes'])
                    self._routes_update_cleanup(p['port']['id'],
                                                None, r['router']['id'], [])

    def test_router_interface_in_use_by_route(self):
        routes = [{'destination': '135.207.0.0/16',
                   'nexthop': '10.0.1.3'}]
        with self.router() as r:
            with self.subnet(cidr='10.0.1.0/24') as s:
                with self.port(subnet=s) as p:
                    body = self._routes_update_prepare(r['router']['id'],
                                                       None, p['port']['id'],
                                                       routes)
                    self.assertEqual(routes, body['router']['routes'])
                    self._router_interface_action(
                        'remove',
                        r['router']['id'],
                        None,
                        p['port']['id'],
                        expected_code=exc.HTTPConflict.code)

                    self._routes_update_cleanup(p['port']['id'],
                                                None, r['router']['id'], [])

    def test_route_update_with_multi_routes(self):
        routes = [{'destination': '135.207.0.0/16',
                   'nexthop': '10.0.1.3'},
                  {'destination': '12.0.0.0/8',
                   'nexthop': '10.0.1.4'},
                  {'destination': '141.212.0.0/16',
                   'nexthop': '10.0.1.5'}]
        with self.router() as r:
            with self.subnet(cidr='10.0.1.0/24') as s:
                with self.port(subnet=s) as p:
                    body = self._routes_update_prepare(r['router']['id'],
                                                       None, p['port']['id'],
                                                       routes)
                    self.assertEqual(
                        sorted(body['router']['routes'],
                               key=utils.safe_sort_key),
                        sorted(routes, key=utils.safe_sort_key))
                    self._routes_update_cleanup(p['port']['id'],
                                                None, r['router']['id'], [])

    def test_routes_update_for_multiple_routers(self):
        routes1 = [{'destination': '135.207.0.0/16',
                   'nexthop': '10.0.0.3'}]
        routes2 = [{'destination': '12.0.0.0/8',
                   'nexthop': '10.0.0.4'}]
        with self.router() as r1,\
                self.router() as r2,\
                self.subnet(cidr='10.0.0.0/24') as s:
            with self.port(subnet=s) as p1, self.port(subnet=s) as p2:
                body = self._routes_update_prepare(r1['router']['id'],
                                                   None, p1['port']['id'],
                                                   routes1)
                self.assertEqual(routes1, body['router']['routes'])

                body = self._routes_update_prepare(r2['router']['id'],
                                                   None, p2['port']['id'],
                                                   routes2)
                self.assertEqual(routes2, body['router']['routes'])

                self._routes_update_cleanup(p1['port']['id'],
                                            None, r1['router']['id'], [])
                self._routes_update_cleanup(p2['port']['id'],
                                            None, r2['router']['id'], [])

    def test_router_update_delete_routes(self):