diff --git a/openstack_dashboard/dashboards/admin/routers/tables.py b/openstack_dashboard/dashboards/admin/routers/tables.py index 1012a87524..58342c6888 100644 --- a/openstack_dashboard/dashboards/admin/routers/tables.py +++ b/openstack_dashboard/dashboards/admin/routers/tables.py @@ -21,19 +21,6 @@ from openstack_dashboard.dashboards.project.routers import tables as r_tables class DeleteRouter(r_tables.DeleteRouter): redirect_url = "horizon:admin:routers:index" - policy_rules = (("network", "delete_router"),) - - def delete(self, request, obj_id): - search_opts = {'device_owner': 'network:router_interface', - 'device_id': obj_id} - ports = api.neutron.port_list(request, **search_opts) - for port in ports: - api.neutron.router_remove_interface(request, obj_id, - port_id=port.id) - super(DeleteRouter, self).delete(request, obj_id) - - def allowed(self, request, router=None): - return True class EditRouter(r_tables.EditRouter): diff --git a/openstack_dashboard/dashboards/admin/routers/tests.py b/openstack_dashboard/dashboards/admin/routers/tests.py index dcdf9ec274..eeb1559a3b 100644 --- a/openstack_dashboard/dashboards/admin/routers/tests.py +++ b/openstack_dashboard/dashboards/admin/routers/tests.py @@ -15,6 +15,7 @@ from django.core.urlresolvers import reverse from django import http +from mox import IgnoreArg # noqa from mox import IsA # noqa from openstack_dashboard import api @@ -80,3 +81,84 @@ class RouterTests(test.BaseAdminViewTests, r_test.RouterTests): table_data[0]['external_gateway_info']['network']) self.assertTemplateUsed(res, '%s/routers/index.html' % self.DASHBOARD) self.assertMessageCount(res, error=1) + + @test.create_stubs({api.neutron: ('router_list', 'network_list', + 'port_list', 'router_delete',), + api.keystone: ('tenant_list',)}) + def test_router_delete(self): + router = self.routers.first() + tenants = self.tenants.list() + api.neutron.router_list( + IsA(http.HttpRequest), + search_opts=None).AndReturn(self.routers.list()) + api.keystone.tenant_list(IsA(http.HttpRequest))\ + .AndReturn([tenants, False]) + self._mock_external_network_list() + api.neutron.router_list( + IsA(http.HttpRequest), + search_opts=None).AndReturn(self.routers.list()) + api.keystone.tenant_list(IsA(http.HttpRequest))\ + .AndReturn([tenants, False]) + self._mock_external_network_list() + api.neutron.port_list(IsA(http.HttpRequest), + device_id=router.id, device_owner=IgnoreArg())\ + .AndReturn([]) + api.neutron.router_delete(IsA(http.HttpRequest), router.id) + api.neutron.router_list( + IsA(http.HttpRequest), + search_opts=None).AndReturn(self.routers.list()) + api.keystone.tenant_list(IsA(http.HttpRequest))\ + .AndReturn([tenants, False]) + self._mock_external_network_list() + self.mox.ReplayAll() + + res = self.client.get(self.INDEX_URL) + + formData = {'action': 'Routers__delete__' + router.id} + res = self.client.post(self.INDEX_URL, formData, follow=True) + self.assertNoFormErrors(res) + self.assertMessageCount(response=res, success=1) + self.assertIn('Deleted Router: ' + router.name, res.content) + + @test.create_stubs({api.neutron: ('router_list', 'network_list', + 'port_list', 'router_remove_interface', + 'router_delete',), + api.keystone: ('tenant_list',)}) + def test_router_with_interface_delete(self): + router = self.routers.first() + ports = self.ports.list() + tenants = self.tenants.list() + api.neutron.router_list( + IsA(http.HttpRequest), + search_opts=None).AndReturn(self.routers.list()) + api.keystone.tenant_list(IsA(http.HttpRequest))\ + .AndReturn([tenants, False]) + self._mock_external_network_list() + api.neutron.router_list( + IsA(http.HttpRequest), + search_opts=None).AndReturn(self.routers.list()) + api.keystone.tenant_list(IsA(http.HttpRequest))\ + .AndReturn([tenants, False]) + self._mock_external_network_list() + api.neutron.port_list(IsA(http.HttpRequest), + device_id=router.id, device_owner=IgnoreArg())\ + .AndReturn(ports) + for port in ports: + api.neutron.router_remove_interface(IsA(http.HttpRequest), + router.id, port_id=port.id) + api.neutron.router_delete(IsA(http.HttpRequest), router.id) + api.neutron.router_list( + IsA(http.HttpRequest), + search_opts=None).AndReturn(self.routers.list()) + api.keystone.tenant_list(IsA(http.HttpRequest))\ + .AndReturn([tenants, False]) + self._mock_external_network_list() + self.mox.ReplayAll() + + res = self.client.get(self.INDEX_URL) + + formData = {'action': 'Routers__delete__' + router.id} + res = self.client.post(self.INDEX_URL, formData, follow=True) + self.assertNoFormErrors(res) + self.assertMessageCount(response=res, success=1) + self.assertIn('Deleted Router: ' + router.name, res.content) diff --git a/openstack_dashboard/dashboards/project/routers/tables.py b/openstack_dashboard/dashboards/project/routers/tables.py index 165616fc85..41fecbeb6a 100644 --- a/openstack_dashboard/dashboards/project/routers/tables.py +++ b/openstack_dashboard/dashboards/project/routers/tables.py @@ -53,9 +53,14 @@ class DeleteRouter(policy.PolicyTargetMixin, tables.DeleteAction): policy_rules = (("network", "delete_router"),) def delete(self, request, obj_id): - obj = self.table.get_object_by_id(obj_id) - name = self.table.get_object_display(obj) try: + # detach all interfaces before attempting to delete the router + search_opts = {'device_owner': 'network:router_interface', + 'device_id': obj_id} + ports = api.neutron.port_list(request, **search_opts) + for port in ports: + api.neutron.router_remove_interface(request, obj_id, + port_id=port.id) api.neutron.router_delete(request, obj_id) except q_ext.NeutronClientException as e: msg = _('Unable to delete router "%s"') % e @@ -64,6 +69,8 @@ class DeleteRouter(policy.PolicyTargetMixin, tables.DeleteAction): redirect = reverse(self.redirect_url) raise exceptions.Http302(redirect, message=msg) except Exception: + obj = self.table.get_object_by_id(obj_id) + name = self.table.get_object_display(obj) msg = _('Unable to delete router "%s"') % name LOG.info(msg) exceptions.handle(request, msg) diff --git a/openstack_dashboard/dashboards/project/routers/tests.py b/openstack_dashboard/dashboards/project/routers/tests.py index dd2d4f5c0e..4fe00365ef 100644 --- a/openstack_dashboard/dashboards/project/routers/tests.py +++ b/openstack_dashboard/dashboards/project/routers/tests.py @@ -16,6 +16,7 @@ import copy from django.core.urlresolvers import reverse from django import http +from mox import IgnoreArg # noqa from mox import IsA # noqa from openstack_dashboard import api @@ -146,6 +147,89 @@ class RouterTests(test.TestCase): args=[router.id])) self.assertRedirectsNoFollow(res, self.INDEX_URL) + @test.create_stubs({api.neutron: ('router_list', 'network_list', + 'port_list', 'router_delete',), + quotas: ('tenant_quota_usages',)}) + def test_router_delete(self): + router = self.routers.first() + quota_data = self.quota_usages.first() + quota_data['routers']['available'] = 5 + api.neutron.router_list( + IsA(http.HttpRequest), + tenant_id=self.tenant.id, + search_opts=None).AndReturn(self.routers.list()) + quotas.tenant_quota_usages( + IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(quota_data) + self._mock_external_network_list() + api.neutron.router_list( + IsA(http.HttpRequest), + tenant_id=self.tenant.id, + search_opts=None).AndReturn(self.routers.list()) + self._mock_external_network_list() + api.neutron.router_list( + IsA(http.HttpRequest), + tenant_id=self.tenant.id, + search_opts=None).AndReturn(self.routers.list()) + self._mock_external_network_list() + api.neutron.port_list(IsA(http.HttpRequest), + device_id=router.id, device_owner=IgnoreArg())\ + .AndReturn([]) + api.neutron.router_delete(IsA(http.HttpRequest), router.id) + self.mox.ReplayAll() + + res = self.client.get(self.INDEX_URL) + + formData = {'action': 'Routers__delete__' + router.id} + res = self.client.post(self.INDEX_URL, formData, follow=True) + self.assertNoFormErrors(res) + self.assertMessageCount(response=res, success=1) + self.assertIn('Deleted Router: ' + router.name, res.content) + + @test.create_stubs({api.neutron: ('router_list', 'network_list', + 'port_list', 'router_remove_interface', + 'router_delete',), + quotas: ('tenant_quota_usages',)}) + def test_router_with_interface_delete(self): + router = self.routers.first() + ports = self.ports.list() + quota_data = self.quota_usages.first() + quota_data['routers']['available'] = 5 + api.neutron.router_list( + IsA(http.HttpRequest), + tenant_id=self.tenant.id, + search_opts=None).AndReturn(self.routers.list()) + quotas.tenant_quota_usages( + IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(quota_data) + self._mock_external_network_list() + api.neutron.router_list( + IsA(http.HttpRequest), + tenant_id=self.tenant.id, + search_opts=None).AndReturn(self.routers.list()) + self._mock_external_network_list() + api.neutron.router_list( + IsA(http.HttpRequest), + tenant_id=self.tenant.id, + search_opts=None).AndReturn(self.routers.list()) + self._mock_external_network_list() + api.neutron.port_list(IsA(http.HttpRequest), + device_id=router.id, device_owner=IgnoreArg())\ + .AndReturn(ports) + for port in ports: + api.neutron.router_remove_interface(IsA(http.HttpRequest), + router.id, port_id=port.id) + api.neutron.router_delete(IsA(http.HttpRequest), router.id) + self.mox.ReplayAll() + + res = self.client.get(self.INDEX_URL) + + formData = {'action': 'Routers__delete__' + router.id} + res = self.client.post(self.INDEX_URL, formData, follow=True) + self.assertNoFormErrors(res) + self.assertMessageCount(response=res, success=1) + self.assertIn('Deleted Router: ' + router.name, res.content) + class RouterActionTests(test.TestCase): DASHBOARD = 'project'