From 80f78f12e599a225dc8390f6beb4335217775597 Mon Sep 17 00:00:00 2001 From: Akihiro MOTOKI Date: Tue, 5 Mar 2013 22:58:12 +0900 Subject: [PATCH] Allow users to create a rich network topology Fixes bug 1146926 This commit enables Quantum Router support to create a router interface with a specified IP address. Previously we can connect one network (subnet) to only one router and a topology we can create is limited. By this commit we can connect one network to multiple routers and create a rich network topology through Dashboard. This commit also fixes the folowing bugs: * subnet_id in "Add Interface" form and network_id in "Set Gateway" form should be required=True since they are required fields. * An error message is display twice when the above two forms. Change-Id: Ifb90f70a4468fdf4e8ba356080cd0333f10e595f --- openstack_dashboard/api/quantum.py | 3 +- .../dashboards/project/routers/ports/forms.py | 82 +++++++++++++++---- .../project/routers/ports/tables.py | 5 ++ .../templates/routers/ports/_create.html | 7 +- .../dashboards/project/routers/tests.py | 80 +++++++++++++++++- 5 files changed, 159 insertions(+), 18 deletions(-) diff --git a/openstack_dashboard/api/quantum.py b/openstack_dashboard/api/quantum.py index 1bd5ed9ac..b04fadcee 100644 --- a/openstack_dashboard/api/quantum.py +++ b/openstack_dashboard/api/quantum.py @@ -412,7 +412,8 @@ def router_add_interface(request, router_id, subnet_id=None, port_id=None): body['subnet_id'] = subnet_id if port_id: body['port_id'] = port_id - quantumclient(request).add_interface_router(router_id, body) + client = quantumclient(request) + return client.add_interface_router(router_id, body) def router_remove_interface(request, router_id, subnet_id=None, port_id=None): diff --git a/openstack_dashboard/dashboards/project/routers/ports/forms.py b/openstack_dashboard/dashboards/project/routers/ports/forms.py index dcaeba540..51a85a3f2 100644 --- a/openstack_dashboard/dashboards/project/routers/ports/forms.py +++ b/openstack_dashboard/dashboards/project/routers/ports/forms.py @@ -22,13 +22,19 @@ from django.utils.translation import ugettext_lazy as _ from horizon import forms from horizon import messages from horizon import exceptions +from horizon.utils import fields from openstack_dashboard import api LOG = logging.getLogger(__name__) class AddInterface(forms.SelfHandlingForm): - subnet_id = forms.ChoiceField(label=_("Subnet"), required=False) + subnet_id = forms.ChoiceField(label=_("Subnet")) + ip_address = fields.IPField( + label=_("IP Address (optional)"), required=False, initial="", + help_text=_("You can specify an IP address of the interface " + "created if you want (e.g. 192.168.0.254)."), + version=fields.IPv4 | fields.IPv6, mask=False) router_name = forms.CharField(label=_("Router Name"), widget=forms.TextInput( attrs={'readonly': 'readonly'})) @@ -70,24 +76,73 @@ class AddInterface(forms.SelfHandlingForm): return choices def handle(self, request, data): + if data['ip_address']: + port = self._add_interface_by_port(request, data) + else: + port = self._add_interface_by_subnet(request, data) + msg = _('Interface added') + if port: + msg += ' ' + port.fixed_ips[0]['ip_address'] + LOG.debug(msg) + messages.success(request, msg) + return True + + def _add_interface_by_subnet(self, request, data): + router_id = data['router_id'] try: - api.quantum.router_add_interface(request, - data['router_id'], - subnet_id=data['subnet_id']) - msg = _('Interface added') - LOG.debug(msg) - messages.success(request, msg) - return True + router_inf = api.quantum.router_add_interface( + request, router_id, subnet_id=data['subnet_id']) except Exception as e: - msg = _('Failed to add_interface %s') % e.message + self._handle_error(request, router_id, e) + try: + port = api.quantum.port_get(request, router_inf['port_id']) + except: + # Ignore an error when port_get() since it is just + # to get an IP address for the interface. + port = None + return port + + def _add_interface_by_port(self, request, data): + router_id = data['router_id'] + subnet_id = data['subnet_id'] + try: + subnet = api.quantum.subnet_get(request, subnet_id) + except: + msg = _('Unable to get subnet "%s"') % subnet_id + self._handle_error(request, router_id, msg) + try: + ip_address = data['ip_address'] + body = {'network_id': subnet.network_id, + 'fixed_ips': [{'subnet_id': subnet.id, + 'ip_address': ip_address}]} + port = api.quantum.port_create(request, **body) + except Exception as e: + self._handle_error(request, router_id, e) + try: + api.quantum.router_add_interface(request, router_id, + port_id=port.id) + except Exception as e: + self._delete_port(request, port) + self._handle_error(request, router_id, e) + return port + + def _handle_error(self, request, router_id, reason): + msg = _('Failed to add_interface: %s') % reason + LOG.info(msg) + redirect = reverse(self.failure_url, args=[router_id]) + exceptions.handle(request, msg, redirect=redirect) + + def _delete_port(self, request, port): + try: + api.quantum.port_delete(request, port.id) + except: + msg = _('Failed to delete port %s') % port.id LOG.info(msg) - messages.error(request, msg) - redirect = reverse(self.failure_url, args=[data['router_id']]) - exceptions.handle(request, msg, redirect=redirect) + exceptions.handle(request, msg) class SetGatewayForm(forms.SelfHandlingForm): - network_id = forms.ChoiceField(label=_("External Network"), required=False) + network_id = forms.ChoiceField(label=_("External Network")) router_name = forms.CharField(label=_("Router Name"), widget=forms.TextInput( attrs={'readonly': 'readonly'})) @@ -132,6 +187,5 @@ class SetGatewayForm(forms.SelfHandlingForm): except Exception as e: msg = _('Failed to set gateway %s') % e.message LOG.info(msg) - messages.error(request, msg) redirect = reverse(self.failure_url) exceptions.handle(request, msg, redirect=redirect) diff --git a/openstack_dashboard/dashboards/project/routers/ports/tables.py b/openstack_dashboard/dashboards/project/routers/ports/tables.py index 2c692643e..b4ad4d6c1 100644 --- a/openstack_dashboard/dashboards/project/routers/ports/tables.py +++ b/openstack_dashboard/dashboards/project/routers/ports/tables.py @@ -71,6 +71,11 @@ class RemoveInterface(tables.DeleteAction): args=[router_id]) exceptions.handle(request, msg, redirect=redirect) + def allowed(self, request, datum=None): + if datum and datum['device_owner'] == 'network:router_gateway': + return False + return True + class PortsTable(tables.DataTable): name = tables.Column("name", diff --git a/openstack_dashboard/dashboards/project/routers/templates/routers/ports/_create.html b/openstack_dashboard/dashboards/project/routers/templates/routers/ports/_create.html index c3629468d..c17dd9cad 100644 --- a/openstack_dashboard/dashboards/project/routers/templates/routers/ports/_create.html +++ b/openstack_dashboard/dashboards/project/routers/templates/routers/ports/_create.html @@ -15,7 +15,12 @@

{% trans "Description" %}:

-

{% trans "You can connect a specified subnet to the router." %}

+

+ {% trans "You can connect a specified subnet to the router." %} +

+

+ {% trans "The default IP address of the interface created is a gateway of the selected subnet. You can specify another IP address of the interface here. You must select a subnet to which the specified IP address belongs to from the above list." %} +

{% endblock %} diff --git a/openstack_dashboard/dashboards/project/routers/tests.py b/openstack_dashboard/dashboards/project/routers/tests.py index 8daeb1883..b8d07eefb 100644 --- a/openstack_dashboard/dashboards/project/routers/tests.py +++ b/openstack_dashboard/dashboards/project/routers/tests.py @@ -150,12 +150,21 @@ class RouterActionTests(test.TestCase): def _test_router_addinterface(self, raise_error=False): router = self.routers.first() subnet = self.subnets.first() + port = self.ports.first() + add_interface = api.quantum.router_add_interface( IsA(http.HttpRequest), router.id, subnet_id=subnet.id) if raise_error: add_interface.AndRaise(self.exceptions.quantum) else: - add_interface.AndReturn(None) + add_interface.AndReturn({'subnet_id': subnet.id, + 'port_id': port.id}) + api.quantum.port_get(IsA(http.HttpRequest), port.id)\ + .AndReturn(port) + self._check_router_addinterface(router, subnet) + + def _check_router_addinterface(self, router, subnet, ip_address=''): + # mock APIs used to show router detail api.quantum.router_get(IsA(http.HttpRequest), router.id)\ .AndReturn(router) self._mock_network_list(router['tenant_id']) @@ -163,7 +172,8 @@ class RouterActionTests(test.TestCase): form_data = {'router_id': router.id, 'router_name': router.name, - 'subnet_id': subnet.id} + 'subnet_id': subnet.id, + 'ip_address': ip_address} url = reverse('horizon:%s:routers:addinterface' % self.DASHBOARD, args=[router.id]) @@ -174,6 +184,7 @@ class RouterActionTests(test.TestCase): @test.create_stubs({api.quantum: ('router_get', 'router_add_interface', + 'port_get', 'network_list')}) def test_router_addinterface(self): self._test_router_addinterface() @@ -184,6 +195,71 @@ class RouterActionTests(test.TestCase): def test_router_addinterface_exception(self): self._test_router_addinterface(raise_error=True) + def _test_router_addinterface_ip_addr(self, errors=[]): + router = self.routers.first() + subnet = self.subnets.first() + port = self.ports.first() + ip_addr = port['fixed_ips'][0]['ip_address'] + self._setup_mock_addinterface_ip_addr(router, subnet, port, + ip_addr, errors) + self._check_router_addinterface(router, subnet, ip_addr) + + def _setup_mock_addinterface_ip_addr(self, router, subnet, port, + ip_addr, errors=[]): + subnet_get = api.quantum.subnet_get(IsA(http.HttpRequest), subnet.id) + if 'subnet_get' in errors: + subnet_get.AndRaise(self.exceptions.quantum) + return + subnet_get.AndReturn(subnet) + + params = {'network_id': subnet.network_id, + 'fixed_ips': [{'subnet_id': subnet.id, + 'ip_address': ip_addr}]} + port_create = api.quantum.port_create(IsA(http.HttpRequest), **params) + if 'port_create' in errors: + port_create.AndRaise(self.exceptions.quantum) + return + port_create.AndReturn(port) + + add_inf = api.quantum.router_add_interface( + IsA(http.HttpRequest), router.id, port_id=port.id) + if 'add_interface' not in errors: + return + + add_inf.AndRaise(self.exceptions.quantum) + port_delete = api.quantum.port_delete(IsA(http.HttpRequest), port.id) + if 'port_delete' in errors: + port_delete.AndRaise(self.exceptions.quantum) + + @test.create_stubs({api.quantum: ('router_add_interface', 'subnet_get', + 'port_create', + 'router_get', 'network_list')}) + def test_router_addinterface_ip_addr(self): + self._test_router_addinterface_ip_addr() + + @test.create_stubs({api.quantum: ('subnet_get', + 'router_get', 'network_list')}) + def test_router_addinterface_ip_addr_exception_subnet_get(self): + self._test_router_addinterface_ip_addr(errors=['subnet_get']) + + @test.create_stubs({api.quantum: ('subnet_get', 'port_create', + 'router_get', 'network_list')}) + def test_router_addinterface_ip_addr_exception_port_create(self): + self._test_router_addinterface_ip_addr(errors=['port_create']) + + @test.create_stubs({api.quantum: ('router_add_interface', 'subnet_get', + 'port_create', 'port_delete', + 'router_get', 'network_list')}) + def test_router_addinterface_ip_addr_exception_add_interface(self): + self._test_router_addinterface_ip_addr(errors=['add_interface']) + + @test.create_stubs({api.quantum: ('router_add_interface', 'subnet_get', + 'port_create', 'port_delete', + 'router_get', 'network_list')}) + def test_router_addinterface_ip_addr_exception_port_delete(self): + self._test_router_addinterface_ip_addr(errors=['add_interface', + 'port_delete']) + @test.create_stubs({api.quantum: ('router_get', 'router_add_gateway', 'network_list')})