From edcd22244fbbf939f22575d4070c317c3b08067f Mon Sep 17 00:00:00 2001 From: Trygve Vea Date: Fri, 20 Oct 2017 21:13:37 +0000 Subject: [PATCH] Add a checkbox to disable SNAT on routers When creating a router, or when setting a gateway on a router - a checkbox is displayed, which can be unchecked to disable SNAT. Change-Id: I8bc040018645fe2bde534b7d48e14c17984cc9c4 Closes-bug: #1673076 --- openstack_dashboard/api/neutron.py | 13 ++++- .../dashboards/project/routers/forms.py | 10 ++++ .../dashboards/project/routers/ports/forms.py | 21 ++++++- .../routers/templates/routers/_create.html | 3 + .../dashboards/project/routers/tests.py | 56 +++++++++++++++++-- .../dashboards/project/routers/views.py | 11 ++++ 6 files changed, 104 insertions(+), 10 deletions(-) diff --git a/openstack_dashboard/api/neutron.py b/openstack_dashboard/api/neutron.py index 29c23366ae..6ece998dd5 100644 --- a/openstack_dashboard/api/neutron.py +++ b/openstack_dashboard/api/neutron.py @@ -1264,8 +1264,10 @@ def router_remove_interface(request, router_id, subnet_id=None, port_id=None): @profiler.trace -def router_add_gateway(request, router_id, network_id): +def router_add_gateway(request, router_id, network_id, enable_snat=None): body = {'network_id': network_id} + if enable_snat is not None: + body['enable_snat'] = enable_snat neutronclient(request).add_gateway_router(router_id, body) @@ -1640,6 +1642,15 @@ FEATURE_MAP = { 'update': 'update_router:ha', } }, + 'ext-gw-mode': { + 'extension': 'ext-gw-mode', + 'policies': { + 'create_router_enable_snat': + 'create_router:external_gateway_info:enable_snat', + 'update_router_enable_snat': + 'update_router:external_gateway_info:enable_snat', + } + }, } diff --git a/openstack_dashboard/dashboards/project/routers/forms.py b/openstack_dashboard/dashboards/project/routers/forms.py index 00000a8644..c83438855b 100644 --- a/openstack_dashboard/dashboards/project/routers/forms.py +++ b/openstack_dashboard/dashboards/project/routers/forms.py @@ -39,6 +39,9 @@ class CreateForm(forms.SelfHandlingForm): required=False) external_network = forms.ThemableChoiceField(label=_("External Network"), required=False) + enable_snat = forms.BooleanField(label=_("Enable SNAT"), + initial=True, + required=False) mode = forms.ChoiceField(label=_("Router Type")) ha = forms.ChoiceField(label=_("High Availability Mode")) az_hints = forms.MultipleChoiceField( @@ -76,6 +79,10 @@ class CreateForm(forms.SelfHandlingForm): else: del self.fields['external_network'] + self.enable_snat_allowed = self.initial['enable_snat_allowed'] + if (not networks or not self.enable_snat_allowed): + del self.fields['enable_snat'] + try: az_supported = api.neutron.is_extension_supported( self.request, 'router_availability_zone') @@ -116,6 +123,9 @@ class CreateForm(forms.SelfHandlingForm): if 'external_network' in data and data['external_network']: params['external_gateway_info'] = {'network_id': data['external_network']} + if self.ext_gw_mode_supported and self.enable_snat_allowed: + params['external_gateway_info']['enable_snat'] = \ + data['enable_snat'] if 'az_hints' in data and data['az_hints']: params['availability_zone_hints'] = data['az_hints'] if (self.dvr_allowed and data['mode'] != 'server_default'): diff --git a/openstack_dashboard/dashboards/project/routers/ports/forms.py b/openstack_dashboard/dashboards/project/routers/ports/forms.py index b7d9f5d428..fd15fa27c4 100644 --- a/openstack_dashboard/dashboards/project/routers/ports/forms.py +++ b/openstack_dashboard/dashboards/project/routers/ports/forms.py @@ -145,12 +145,23 @@ class AddInterface(forms.SelfHandlingForm): class SetGatewayForm(forms.SelfHandlingForm): network_id = forms.ThemableChoiceField(label=_("External Network")) + enable_snat = forms.BooleanField(label=_("Enable SNAT"), + initial=True, + required=False) failure_url = 'horizon:project:routers:index' def __init__(self, request, *args, **kwargs): super(SetGatewayForm, self).__init__(request, *args, **kwargs) - c = self.populate_network_id_choices(request) - self.fields['network_id'].choices = c + networks = self.populate_network_id_choices(request) + self.fields['network_id'].choices = networks + self.ext_gw_mode = api.neutron.is_extension_supported( + self.request, 'ext-gw-mode') + self.enable_snat_allowed = api.neutron.get_feature_permission( + self.request, + "ext-gw-mode", + "update_router_enable_snat") + if not self.ext_gw_mode or not self.enable_snat_allowed: + del self.fields['enable_snat'] def populate_network_id_choices(self, request): search_opts = {'router:external': True} @@ -173,9 +184,13 @@ class SetGatewayForm(forms.SelfHandlingForm): def handle(self, request, data): try: + enable_snat = None + if 'enable_snat' in data: + enable_snat = data['enable_snat'] api.neutron.router_add_gateway(request, self.initial['router_id'], - data['network_id']) + data['network_id'], + enable_snat) msg = _('Gateway interface is added') messages.success(request, msg) return True diff --git a/openstack_dashboard/dashboards/project/routers/templates/routers/_create.html b/openstack_dashboard/dashboards/project/routers/templates/routers/_create.html index b52b7e1a52..c7906736c8 100644 --- a/openstack_dashboard/dashboards/project/routers/templates/routers/_create.html +++ b/openstack_dashboard/dashboards/project/routers/templates/routers/_create.html @@ -4,4 +4,7 @@ {% block modal-body-right %}

{% trans "Description:" %}

{% trans "Creates a router with specified parameters." %}

+ {% if enable_snat_allowed %} +

{% trans "Enable SNAT will only have an effect if an external network is set." %}

+ {% endif %} {% endblock %} diff --git a/openstack_dashboard/dashboards/project/routers/tests.py b/openstack_dashboard/dashboards/project/routers/tests.py index 4b8738e477..db3eb405f5 100644 --- a/openstack_dashboard/dashboards/project/routers/tests.py +++ b/openstack_dashboard/dashboards/project/routers/tests.py @@ -284,6 +284,10 @@ class RouterActionTests(RouterMixin, test.TestCase): 'is_extension_supported')}) def test_router_create_post(self): router = self.routers.first() + api.neutron.get_feature_permission(IsA(http.HttpRequest), + "ext-gw-mode", + "create_router_enable_snat")\ + .AndReturn(True) api.neutron.get_feature_permission(IsA(http.HttpRequest), "dvr", "create")\ .AndReturn(False) @@ -315,6 +319,10 @@ class RouterActionTests(RouterMixin, test.TestCase): 'is_extension_supported')}) def test_router_create_post_mode_server_default(self): router = self.routers.first() + api.neutron.get_feature_permission(IsA(http.HttpRequest), + "ext-gw-mode", + "create_router_enable_snat")\ + .AndReturn(True) api.neutron.get_feature_permission(IsA(http.HttpRequest), "dvr", "create")\ .AndReturn(True) @@ -348,6 +356,10 @@ class RouterActionTests(RouterMixin, test.TestCase): 'is_extension_supported')}) def test_dvr_ha_router_create_post(self): router = self.routers.first() + api.neutron.get_feature_permission(IsA(http.HttpRequest), + "ext-gw-mode", + "create_router_enable_snat")\ + .AndReturn(True) api.neutron.get_feature_permission(IsA(http.HttpRequest), "dvr", "create")\ .MultipleTimes().AndReturn(True) @@ -384,6 +396,10 @@ class RouterActionTests(RouterMixin, test.TestCase): 'list_availability_zones')}) def test_az_router_create_post(self): router = self.routers.first() + api.neutron.get_feature_permission(IsA(http.HttpRequest), + "ext-gw-mode", + "create_router_enable_snat")\ + .AndReturn(True) api.neutron.get_feature_permission(IsA(http.HttpRequest), "dvr", "create")\ .MultipleTimes().AndReturn(False) @@ -422,6 +438,10 @@ class RouterActionTests(RouterMixin, test.TestCase): 'is_extension_supported')}) def test_router_create_post_exception_error_case_409(self): router = self.routers.first() + api.neutron.get_feature_permission(IsA(http.HttpRequest), + "ext-gw-mode", + "create_router_enable_snat")\ + .AndReturn(True) api.neutron.get_feature_permission(IsA(http.HttpRequest), "dvr", "create")\ .MultipleTimes().AndReturn(False) @@ -454,6 +474,10 @@ class RouterActionTests(RouterMixin, test.TestCase): 'network_list')}) def test_router_create_post_exception_error_case_non_409(self): router = self.routers.first() + api.neutron.get_feature_permission(IsA(http.HttpRequest), + "ext-gw-mode", + "create_router_enable_snat")\ + .AndReturn(True) api.neutron.get_feature_permission(IsA(http.HttpRequest), "dvr", "create")\ .MultipleTimes().AndReturn(False) @@ -730,24 +754,34 @@ class RouterActionTests(RouterMixin, test.TestCase): @test.create_stubs({api.neutron: ('router_get', 'router_add_gateway', - 'network_list')}) + 'network_list', + 'is_extension_supported')}) def test_router_add_gateway(self): router = self.routers.first() network = self.networks.first() api.neutron.router_add_gateway( IsA(http.HttpRequest), router.id, - network.id).AndReturn(None) + network.id, + True).AndReturn(None) api.neutron.router_get( IsA(http.HttpRequest), router.id).AndReturn(router) search_opts = {'router:external': True} api.neutron.network_list( IsA(http.HttpRequest), **search_opts).AndReturn([network]) + api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'ext-gw-mode')\ + .AndReturn(True) + api.neutron.get_feature_permission(IsA(http.HttpRequest), + "ext-gw-mode", + "update_router_enable_snat")\ + .AndReturn(True) self.mox.ReplayAll() form_data = {'router_id': router.id, 'router_name': router.name, - 'network_id': network.id} + 'network_id': network.id, + 'enable_snat': True} url = reverse('horizon:%s:routers:setgateway' % self.DASHBOARD, args=[router.id]) @@ -758,24 +792,34 @@ class RouterActionTests(RouterMixin, test.TestCase): @test.create_stubs({api.neutron: ('router_get', 'router_add_gateway', - 'network_list')}) + 'network_list', + 'is_extension_supported')}) def test_router_add_gateway_exception(self): router = self.routers.first() network = self.networks.first() api.neutron.router_add_gateway( IsA(http.HttpRequest), router.id, - network.id).AndRaise(self.exceptions.neutron) + network.id, + True).AndRaise(self.exceptions.neutron) api.neutron.router_get( IsA(http.HttpRequest), router.id).AndReturn(router) search_opts = {'router:external': True} api.neutron.network_list( IsA(http.HttpRequest), **search_opts).AndReturn([network]) + api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'ext-gw-mode')\ + .AndReturn(True) + api.neutron.get_feature_permission(IsA(http.HttpRequest), + "ext-gw-mode", + "update_router_enable_snat")\ + .AndReturn(True) self.mox.ReplayAll() form_data = {'router_id': router.id, 'router_name': router.name, - 'network_id': network.id} + 'network_id': network.id, + 'enable_snat': True} url = reverse('horizon:%s:routers:setgateway' % self.DASHBOARD, args=[router.id]) diff --git a/openstack_dashboard/dashboards/project/routers/views.py b/openstack_dashboard/dashboards/project/routers/views.py index 31b927f789..4c428f3305 100644 --- a/openstack_dashboard/dashboards/project/routers/views.py +++ b/openstack_dashboard/dashboards/project/routers/views.py @@ -180,6 +180,17 @@ class CreateView(forms.ModalFormView): submit_label = _("Create Router") submit_url = reverse_lazy("horizon:project:routers:create") + def get_context_data(self, **kwargs): + context = super(CreateView, self).get_context_data(**kwargs) + context['enable_snat_allowed'] = self.initial['enable_snat_allowed'] + return context + + def get_initial(self): + enable_snat_allowed = api.neutron.get_feature_permission( + self.request, 'ext-gw-mode', 'create_router_enable_snat') + self.initial['enable_snat_allowed'] = enable_snat_allowed + return super(CreateView, self).get_initial() + class UpdateView(forms.ModalFormView): form_class = project_forms.UpdateForm