summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNachi Ueno <nachi@nttmcl.com>2012-09-20 07:10:06 +0000
committerNachi Ueno <nachi@nttmcl.com>2013-01-07 20:24:57 -0800
commiteed092a5bb725e58ec45c0f469c2b8c73d59c46a (patch)
tree8421fa3a3ad7890c4774f49e685bfa86058fc111
parent31d55e503d578f3af86fed15a37127aff9871ecd (diff)
downloadhorizon-eed092a5bb725e58ec45c0f469c2b8c73d59c46a.tar.gz
Support Quantum L3 function
Implements bp quantum-l3-support Implemented basic CRD for router Add/remove interface support Support set gateway and clear gateway Change-Id: Ie4cac962eb8fadc021c80cf05e2aa63caab3c00a
-rw-r--r--openstack_dashboard/api/quantum.py61
-rw-r--r--openstack_dashboard/dashboards/admin/dashboard.py2
-rw-r--r--openstack_dashboard/dashboards/admin/routers/__init__.py0
-rw-r--r--openstack_dashboard/dashboards/admin/routers/forms.py71
-rw-r--r--openstack_dashboard/dashboards/admin/routers/panel.py29
-rw-r--r--openstack_dashboard/dashboards/admin/routers/ports/__init__.py0
-rw-r--r--openstack_dashboard/dashboards/admin/routers/ports/forms.py31
-rw-r--r--openstack_dashboard/dashboards/admin/routers/ports/tables.py64
-rw-r--r--openstack_dashboard/dashboards/admin/routers/ports/tabs.py31
-rw-r--r--openstack_dashboard/dashboards/admin/routers/ports/urls.py24
-rw-r--r--openstack_dashboard/dashboards/admin/routers/ports/views.py43
-rw-r--r--openstack_dashboard/dashboards/admin/routers/tables.py71
-rw-r--r--openstack_dashboard/dashboards/admin/routers/tabs.py28
-rw-r--r--openstack_dashboard/dashboards/admin/routers/templates/routers/_create.html21
-rw-r--r--openstack_dashboard/dashboards/admin/routers/templates/routers/_detail_overview.html15
-rw-r--r--openstack_dashboard/dashboards/admin/routers/templates/routers/create.html11
-rw-r--r--openstack_dashboard/dashboards/admin/routers/templates/routers/detail.html15
-rw-r--r--openstack_dashboard/dashboards/admin/routers/templates/routers/index.html11
-rw-r--r--openstack_dashboard/dashboards/admin/routers/tests.py104
-rw-r--r--openstack_dashboard/dashboards/admin/routers/urls.py34
-rw-r--r--openstack_dashboard/dashboards/admin/routers/views.py76
-rw-r--r--openstack_dashboard/dashboards/project/dashboard.py3
-rw-r--r--openstack_dashboard/dashboards/project/routers/__init__.py0
-rw-r--r--openstack_dashboard/dashboards/project/routers/forms.py41
-rw-r--r--openstack_dashboard/dashboards/project/routers/panel.py29
-rw-r--r--openstack_dashboard/dashboards/project/routers/ports/__init__.py0
-rw-r--r--openstack_dashboard/dashboards/project/routers/ports/forms.py132
-rw-r--r--openstack_dashboard/dashboards/project/routers/ports/tables.py103
-rw-r--r--openstack_dashboard/dashboards/project/routers/ports/tabs.py47
-rw-r--r--openstack_dashboard/dashboards/project/routers/ports/urls.py24
-rw-r--r--openstack_dashboard/dashboards/project/routers/ports/views.py100
-rw-r--r--openstack_dashboard/dashboards/project/routers/tables.py90
-rw-r--r--openstack_dashboard/dashboards/project/routers/tabs.py45
-rw-r--r--openstack_dashboard/dashboards/project/routers/templates/routers/_create.html21
-rw-r--r--openstack_dashboard/dashboards/project/routers/templates/routers/_detail_overview.html15
-rw-r--r--openstack_dashboard/dashboards/project/routers/templates/routers/create.html11
-rw-r--r--openstack_dashboard/dashboards/project/routers/templates/routers/detail.html15
-rw-r--r--openstack_dashboard/dashboards/project/routers/templates/routers/index.html11
-rw-r--r--openstack_dashboard/dashboards/project/routers/templates/routers/ports/_create.html25
-rw-r--r--openstack_dashboard/dashboards/project/routers/templates/routers/ports/_setgateway.html25
-rw-r--r--openstack_dashboard/dashboards/project/routers/templates/routers/ports/create.html11
-rw-r--r--openstack_dashboard/dashboards/project/routers/templates/routers/ports/setgateway.html11
-rw-r--r--openstack_dashboard/dashboards/project/routers/tests.py235
-rw-r--r--openstack_dashboard/dashboards/project/routers/urls.py34
-rw-r--r--openstack_dashboard/dashboards/project/routers/views.py105
-rw-r--r--openstack_dashboard/test/api_tests/quantum_tests.py67
-rw-r--r--openstack_dashboard/test/test_data/quantum_data.py32
47 files changed, 1968 insertions, 6 deletions
diff --git a/openstack_dashboard/api/quantum.py b/openstack_dashboard/api/quantum.py
index 2c2f26e28..c41309bfc 100644
--- a/openstack_dashboard/api/quantum.py
+++ b/openstack_dashboard/api/quantum.py
@@ -78,6 +78,15 @@ class Port(QuantumAPIDictWrapper):
super(Port, self).__init__(apiresource)
+class Router(QuantumAPIDictWrapper):
+ """Wrapper for quantum routers"""
+
+ def __init__(self, apiresource):
+ #apiresource['admin_state'] = \
+ # 'UP' if apiresource['admin_state_up'] else 'DOWN'
+ super(Router, self).__init__(apiresource)
+
+
IP_VERSION_DICT = {4: 'IPv4', 6: 'IPv6'}
@@ -104,7 +113,7 @@ def network_list(request, **params):
subnet_dict = SortedDict([(s['id'], s) for s in subnets])
# Expand subnet list from subnet_id to values.
for n in networks:
- n['subnets'] = [subnet_dict[s] for s in n['subnets']]
+ n['subnets'] = [subnet_dict.get(s) for s in n.get('subnets', [])]
return [Network(n) for n in networks]
@@ -256,3 +265,53 @@ def port_modify(request, port_id, **kwargs):
body = {'port': kwargs}
port = quantumclient(request).update_port(port_id, body=body).get('port')
return Port(port)
+
+
+def router_create(request, **kwargs):
+ LOG.debug("router_create():, kwargs=%s" % kwargs)
+ body = {'router': {}}
+ body['router'].update(kwargs)
+ router = quantumclient(request).create_router(body=body).get('router')
+ return Router(router)
+
+
+def router_get(request, router_id, **params):
+ router = quantumclient(request).show_router(router_id,
+ **params).get('router')
+ return Router(router)
+
+
+def router_list(request, **params):
+ routers = quantumclient(request).list_routers(**params).get('routers')
+ return [Router(r) for r in routers]
+
+
+def router_delete(request, router_id):
+ quantumclient(request).delete_router(router_id)
+
+
+def router_add_interface(request, router_id, subnet_id=None, port_id=None):
+ body = {}
+ if subnet_id:
+ body['subnet_id'] = subnet_id
+ if port_id:
+ body['port_id'] = port_id
+ quantumclient(request).add_interface_router(router_id, body)
+
+
+def router_remove_interface(request, router_id, subnet_id=None, port_id=None):
+ body = {}
+ if subnet_id:
+ body['subnet_id'] = subnet_id
+ if port_id:
+ body['port_id'] = port_id
+ quantumclient(request).remove_interface_router(router_id, body)
+
+
+def router_add_gateway(request, router_id, network_id):
+ body = {'network_id': network_id}
+ quantumclient(request).add_gateway_router(router_id, body)
+
+
+def router_remove_gateway(request, router_id):
+ quantumclient(request).remove_gateway_router(router_id)
diff --git a/openstack_dashboard/dashboards/admin/dashboard.py b/openstack_dashboard/dashboards/admin/dashboard.py
index e556f97e3..054c250e9 100644
--- a/openstack_dashboard/dashboards/admin/dashboard.py
+++ b/openstack_dashboard/dashboards/admin/dashboard.py
@@ -23,7 +23,7 @@ class SystemPanels(horizon.PanelGroup):
slug = "admin"
name = _("System Panel")
panels = ('overview', 'instances', 'volumes', 'flavors',
- 'images', 'projects', 'users', 'networks', 'info')
+ 'images', 'projects', 'users', 'networks', 'routers', 'info')
class Admin(horizon.Dashboard):
diff --git a/openstack_dashboard/dashboards/admin/routers/__init__.py b/openstack_dashboard/dashboards/admin/routers/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/routers/__init__.py
diff --git a/openstack_dashboard/dashboards/admin/routers/forms.py b/openstack_dashboard/dashboards/admin/routers/forms.py
new file mode 100644
index 000000000..dc83dd7b4
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/routers/forms.py
@@ -0,0 +1,71 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
+#
+# 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.
+
+"""
+Views for managing Quantum Routers.
+"""
+import logging
+
+from django.core.urlresolvers import reverse
+from django.utils.translation import ugettext_lazy as _
+
+from horizon import forms
+from horizon import exceptions
+from horizon import messages
+from openstack_dashboard import api
+
+LOG = logging.getLogger(__name__)
+
+
+class CreateForm(forms.SelfHandlingForm):
+ name = forms.CharField(max_length="255",
+ label=_("Router Name"),
+ required=False)
+ tenant_id = forms.ChoiceField(label=_("Project"))
+ failure_url = 'horizon:admin:routers:index'
+
+ def __init__(self, request, *args, **kwargs):
+ super(CreateForm, self).__init__(request, *args, **kwargs)
+ tenant_choices = [('', _("Select a project"))]
+ try:
+ for tenant in api.keystone.tenant_list(request, admin=True):
+ if tenant.enabled:
+ tenant_choices.append((tenant.id, tenant.name))
+ except:
+ msg = _('Failed to get tenants.')
+ LOG.warn(msg)
+ redirect = reverse(self.failure_url)
+ exceptions.handle(request, msg, redirect=redirect)
+ return False
+
+ self.fields['tenant_id'].choices = tenant_choices
+
+ def handle(self, request, data):
+ try:
+ params = {}
+ if data.get('tenant_id'):
+ params['tenant_id'] = data['tenant_id']
+ router = api.quantum.router_create(request,
+ name=data['name'], **params)
+ message = 'Creating router "%s"' % data['name']
+ messages.info(request, message)
+ return router
+ except:
+ msg = _('Failed to create router "%s".') % data['name']
+ LOG.warn(msg)
+ redirect = reverse(self.failure_url)
+ exceptions.handle(request, msg, redirect=redirect)
+ return False
diff --git a/openstack_dashboard/dashboards/admin/routers/panel.py b/openstack_dashboard/dashboards/admin/routers/panel.py
new file mode 100644
index 000000000..ccb9c996b
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/routers/panel.py
@@ -0,0 +1,29 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
+#
+# 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 django.utils.translation import ugettext_lazy as _
+
+import horizon
+
+from openstack_dashboard.dashboards.admin import dashboard
+
+
+class Routers(horizon.Panel):
+ name = "Routers"
+ slug = 'routers'
+ permissions = ('openstack.services.network',)
+
+dashboard.Admin.register(Routers)
diff --git a/openstack_dashboard/dashboards/admin/routers/ports/__init__.py b/openstack_dashboard/dashboards/admin/routers/ports/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/routers/ports/__init__.py
diff --git a/openstack_dashboard/dashboards/admin/routers/ports/forms.py b/openstack_dashboard/dashboards/admin/routers/ports/forms.py
new file mode 100644
index 000000000..e25fa69c7
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/routers/ports/forms.py
@@ -0,0 +1,31 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
+#
+# 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.
+
+import logging
+
+from openstack_dashboard.dashboards.project.routers.ports import (
+ forms as p_forms)
+
+
+LOG = logging.getLogger(__name__)
+
+
+class AddInterface(p_forms.AddInterface):
+ failure_url = 'horizon:admin:routers:detail'
+
+
+class SetGatewayForm(p_forms.SetGatewayForm):
+ failure_url = 'horizon:admin:routers:detail'
diff --git a/openstack_dashboard/dashboards/admin/routers/ports/tables.py b/openstack_dashboard/dashboards/admin/routers/ports/tables.py
new file mode 100644
index 000000000..f064367f0
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/routers/ports/tables.py
@@ -0,0 +1,64 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 NEC Corporation
+#
+# 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.
+
+import logging
+
+from django.utils.translation import ugettext_lazy as _
+
+from horizon import tables
+from openstack_dashboard.dashboards.project.networks.ports.tables import\
+ get_fixed_ips, get_attached
+from openstack_dashboard.dashboards.project.routers.ports import\
+ tables as r_tables
+from openstack_dashboard.dashboards.project.routers.ports.tables import\
+ get_device_owner
+
+
+LOG = logging.getLogger(__name__)
+
+
+class SetGateway(r_tables.SetGateway):
+ url = "horizon:admin:routers:setgateway"
+
+
+class AddInterface(r_tables.AddInterface):
+ url = "horizon:admin:routers:addinterface"
+
+
+class RemoveInterface(r_tables.RemoveInterface):
+ failure_url = 'horizon:admin:routers:detail'
+
+
+class PortsTable(tables.DataTable):
+ name = tables.Column("name",
+ verbose_name=_("Name"),
+ link="horizon:admin:networks:ports:detail")
+ fixed_ips = tables.Column(get_fixed_ips, verbose_name=_("Fixed IPs"))
+ attached = tables.Column(get_attached, verbose_name=_("Device Attached"))
+ status = tables.Column("status", verbose_name=_("Status"))
+ device_owner = tables.Column(get_device_owner,
+ verbose_name=_("Type"))
+ admin_state = tables.Column("admin_state",
+ verbose_name=_("Admin State"))
+
+ def get_object_display(self, port):
+ return port.id
+
+ class Meta:
+ name = "interfaces"
+ verbose_name = _("Interfaces")
+ table_actions = (AddInterface, SetGateway, RemoveInterface)
+ row_actions = (RemoveInterface, )
diff --git a/openstack_dashboard/dashboards/admin/routers/ports/tabs.py b/openstack_dashboard/dashboards/admin/routers/ports/tabs.py
new file mode 100644
index 000000000..16d63b85d
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/routers/ports/tabs.py
@@ -0,0 +1,31 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
+#
+# 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.
+import logging
+
+from horizon import tabs
+from openstack_dashboard.dashboards.project.routers.ports import tabs as r_tabs
+
+LOG = logging.getLogger(__name__)
+
+
+class OverviewTab(r_tabs.OverviewTab):
+ template_name = "admin/networks/ports/_detail_overview.html"
+ failure_url = "horizon:admin:routers:index"
+
+
+class PortDetailTabs(tabs.TabGroup):
+ slug = "port_details"
+ tabs = (OverviewTab,)
diff --git a/openstack_dashboard/dashboards/admin/routers/ports/urls.py b/openstack_dashboard/dashboards/admin/routers/ports/urls.py
new file mode 100644
index 000000000..1618bc14e
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/routers/ports/urls.py
@@ -0,0 +1,24 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 NTT MCL
+#
+# 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 django.conf.urls.defaults import patterns, url
+
+from .views import DetailView
+
+PORTS = r'^(?P<port_id>[^/]+)/%s$'
+
+urlpatterns = patterns('horizon.dashboards.admin.networks.ports.views',
+ url(PORTS % 'detail', DetailView.as_view(), name='detail'))
diff --git a/openstack_dashboard/dashboards/admin/routers/ports/views.py b/openstack_dashboard/dashboards/admin/routers/ports/views.py
new file mode 100644
index 000000000..7ac47ffcb
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/routers/ports/views.py
@@ -0,0 +1,43 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
+#
+# 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.
+
+import logging
+
+from horizon import tabs
+from .tabs import PortDetailTabs
+from .forms import (AddInterface, SetGatewayForm)
+from openstack_dashboard.dashboards.project.routers.ports import views
+
+
+LOG = logging.getLogger(__name__)
+
+
+class AddInterfaceView(views.AddInterfaceView):
+ form_class = AddInterface
+ template_name = 'admin/routers/ports/create.html'
+ success_url = 'horizon:admin:routers:detail'
+ failure_url = 'horizon:admin:routers:detail'
+
+
+class SetGatewayView(views.SetGatewayView):
+ form_class = SetGatewayForm
+ success_url = 'horizon:admin:routers:detail'
+ failure_url = 'horizon:admin:routers:detail'
+
+
+class DetailView(tabs.TabView):
+ tab_group_class = PortDetailTabs
+ template_name = 'admin/networks/ports/detail.html'
diff --git a/openstack_dashboard/dashboards/admin/routers/tables.py b/openstack_dashboard/dashboards/admin/routers/tables.py
new file mode 100644
index 000000000..85162bec8
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/routers/tables.py
@@ -0,0 +1,71 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
+#
+# 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.
+
+import logging
+
+from django.template.defaultfilters import title
+from django.utils.translation import ugettext_lazy as _
+
+from horizon import tables
+from openstack_dashboard import api
+from openstack_dashboard.dashboards.project.routers import tables as r_tables
+
+
+LOG = logging.getLogger(__name__)
+
+
+class DeleteRouter(r_tables.DeleteRouter):
+ redirect_url = "horizon:admin:routers:index"
+
+ def allowed(self, request, router=None):
+ return True
+
+
+class CreateRouter(tables.LinkAction):
+ name = "create"
+ verbose_name = _("Create Router")
+ url = "horizon:admin:routers:create"
+ classes = ("ajax-modal", "btn-create")
+
+
+class UpdateRow(tables.Row):
+ ajax = True
+
+ def get_data(self, request, router_id):
+ router = api.router_get(request, router_id)
+ return router
+
+
+class RoutersTable(tables.DataTable):
+ tenant = tables.Column("tenant_name", verbose_name=_("Project"))
+ name = tables.Column("name",
+ verbose_name=_("Name"),
+ link="horizon:admin:routers:detail")
+ status = tables.Column("status",
+ filters=(title,),
+ verbose_name=_("Status"),
+ status=True)
+
+ def get_object_display(self, obj):
+ return obj.name
+
+ class Meta:
+ name = "Routers"
+ verbose_name = _("Routers")
+ status_columns = ["status"]
+ row_class = UpdateRow
+ table_actions = (CreateRouter, DeleteRouter)
+ row_actions = (DeleteRouter, )
diff --git a/openstack_dashboard/dashboards/admin/routers/tabs.py b/openstack_dashboard/dashboards/admin/routers/tabs.py
new file mode 100644
index 000000000..4fe386f2a
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/routers/tabs.py
@@ -0,0 +1,28 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
+#
+# 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 horizon import tabs
+from openstack_dashboard.dashboards.project.routers import tabs as r_tabs
+
+
+class OverviewTab(r_tabs.OverviewTab):
+ template_name = ("admin/routers/_detail_overview.html")
+ redirect_url = 'horizon:admin:routers:index'
+
+
+class RouterDetailTabs(tabs.TabGroup):
+ slug = "router_details"
+ tabs = (OverviewTab,)
diff --git a/openstack_dashboard/dashboards/admin/routers/templates/routers/_create.html b/openstack_dashboard/dashboards/admin/routers/templates/routers/_create.html
new file mode 100644
index 000000000..f929e410b
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/routers/templates/routers/_create.html
@@ -0,0 +1,21 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n horizon humanize %}
+
+{% block form_id %}{% endblock %}
+{% block form_action %}{% url horizon:admin:routers:create %}?{{ request.GET.urlencode }}{% endblock %}
+
+{% block modal_id %}create_router_modal{% endblock %}
+{% block modal-header %}{% trans "Create router" %}{% endblock %}
+
+{% block modal-body %}
+ <div class="left">
+ <fieldset>
+ {% include "horizon/common/_form_fields.html" %}
+ </fieldset>
+ </div>
+{% endblock %}
+
+{% block modal-footer %}
+ <input class="btn btn-primary pull-right" type="submit" value="{% trans "Create router" %}" />
+ <a href="{% url horizon:admin:routers:index %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/routers/templates/routers/_detail_overview.html b/openstack_dashboard/dashboards/admin/routers/templates/routers/_detail_overview.html
new file mode 100644
index 000000000..29d86ed18
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/routers/templates/routers/_detail_overview.html
@@ -0,0 +1,15 @@
+{% load i18n sizeformat parse_date %}
+
+<h3>{% trans "Router Overview" %}: {{router.display_name }}</h3>
+
+<div class="info detail">
+ <dl>
+ <dt>{% trans "Name" %}</dt>
+ <dd>{{ router.display_name }}</dd>
+ <dt>{% trans "ID" %}</dt>
+ <dd>{{ router.id }}</dd>
+ <dt>{% trans "Status" %}</dt>
+ <dd>{{ router.status|capfirst }}</dd>
+ </dl>
+</div>
+
diff --git a/openstack_dashboard/dashboards/admin/routers/templates/routers/create.html b/openstack_dashboard/dashboards/admin/routers/templates/routers/create.html
new file mode 100644
index 000000000..d9ca9ba0b
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/routers/templates/routers/create.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}Create router{% endblock %}
+
+{% block page_header %}
+ {% include "horizon/common/_page_header.html" with title=_("Create a router") %}
+{% endblock page_header %}
+
+{% block main %}
+ {% include 'admin/routers/_create.html' %}
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/routers/templates/routers/detail.html b/openstack_dashboard/dashboards/admin/routers/templates/routers/detail.html
new file mode 100644
index 000000000..15c46bce9
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/routers/templates/routers/detail.html
@@ -0,0 +1,15 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Router Details" %}{% endblock %}
+
+{% block page_header %}
+ {% include "horizon/common/_page_header.html" with title=_("Router Detail") %}
+{% endblock page_header %}
+
+{% block main %}
+{% include "admin/routers/_detail_overview.html" %}
+<hr>
+<div id="interfaces">
+ {{ interfaces_table.render }}
+</div>
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/routers/templates/routers/index.html b/openstack_dashboard/dashboards/admin/routers/templates/routers/index.html
new file mode 100644
index 000000000..b169f34c2
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/routers/templates/routers/index.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "routers" %}{% endblock %}
+
+{% block page_header %}
+ {% include "horizon/common/_page_header.html" with title=_("routers") %}
+{% endblock page_header %}
+
+{% block main %}
+ {{ table.render }}
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/routers/tests.py b/openstack_dashboard/dashboards/admin/routers/tests.py
new file mode 100644
index 000000000..786d2a3e6
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/routers/tests.py
@@ -0,0 +1,104 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
+#
+# 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 mox import IsA
+from django import http
+from django.core.urlresolvers import reverse
+
+from openstack_dashboard import api
+from openstack_dashboard.dashboards.project.routers import tests as r_test
+from openstack_dashboard.test import helpers as test
+
+
+class RouterTests(test.BaseAdminViewTests, r_test.RouterTests):
+ DASHBOARD = 'admin'
+ INDEX_URL = reverse('horizon:%s:routers:index' % DASHBOARD)
+ DETAIL_PATH = 'horizon:%s:routers:detail' % DASHBOARD
+
+ @test.create_stubs({api.quantum: ('router_list',),
+ api.keystone: ('tenant_list',)})
+ def test_index(self):
+ tenants = self.tenants.list()
+ api.quantum.router_list(
+ IsA(http.HttpRequest),
+ search_opts=None).AndReturn(self.routers.list())
+ api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\
+ .AndReturn(tenants)
+
+ self.mox.ReplayAll()
+
+ res = self.client.get(self.INDEX_URL)
+
+ self.assertTemplateUsed(res, '%s/routers/index.html' % self.DASHBOARD)
+ routers = res.context['table'].data
+ self.assertItemsEqual(routers, self.routers.list())
+
+ @test.create_stubs({api.quantum: ('router_list',),
+ api.keystone: ('tenant_list',)})
+ def test_index_router_list_exception(self):
+ tenants = self.tenants.list()
+ api.quantum.router_list(
+ IsA(http.HttpRequest),
+ search_opts=None).AndRaise(self.exceptions.quantum)
+ self.mox.ReplayAll()
+
+ res = self.client.get(self.INDEX_URL)
+
+ self.assertTemplateUsed(res, '%s/routers/index.html' % self.DASHBOARD)
+ self.assertEqual(len(res.context['table'].data), 0)
+ self.assertMessageCount(res, error=1)
+
+ @test.create_stubs({api.quantum: ('router_list', 'router_create'),
+ api.keystone: ('tenant_list',)})
+ def test_router_create_post(self):
+ router = self.routers.first()
+ tenants = self.tenants.list()
+ api.quantum.router_create(
+ IsA(http.HttpRequest),
+ name=router.name,
+ tenant_id=router.tenant_id).AndReturn(router)
+ api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\
+ .AndReturn(tenants)
+
+ self.mox.ReplayAll()
+
+ form_data = {'name': router.name,
+ 'tenant_id': router.tenant_id}
+ url = reverse('horizon:%s:routers:create' % self.DASHBOARD)
+ res = self.client.post(url, form_data)
+
+ self.assertNoFormErrors(res)
+ self.assertRedirectsNoFollow(res, self.INDEX_URL)
+
+ @test.create_stubs({api.quantum: ('router_list', 'router_create'),
+ api.keystone: ('tenant_list',)})
+ def test_router_create_post_exception(self):
+ router = self.routers.first()
+ tenants = self.tenants.list()
+ api.quantum.router_create(
+ IsA(http.HttpRequest),
+ name=router.name,
+ tenant_id=router.tenant_id).AndRaise(self.exceptions.quantum)
+ api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\
+ .AndReturn(tenants)
+ self.mox.ReplayAll()
+
+ form_data = {'name': router.name,
+ 'tenant_id': router.tenant_id}
+ url = reverse('horizon:%s:routers:create' % self.DASHBOARD)
+ res = self.client.post(url, form_data)
+
+ self.assertNoFormErrors(res)
+ self.assertRedirectsNoFollow(res, self.INDEX_URL)
diff --git a/openstack_dashboard/dashboards/admin/routers/urls.py b/openstack_dashboard/dashboards/admin/routers/urls.py
new file mode 100644
index 000000000..de299bbf8
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/routers/urls.py
@@ -0,0 +1,34 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
+#
+# 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 django.conf.urls.defaults import patterns, url
+
+from .views import (IndexView, CreateView, DetailView)
+from .ports.views import (AddInterfaceView, SetGatewayView)
+
+
+urlpatterns = patterns('horizon.dashboards.admin.routers.views',
+ url(r'^$', IndexView.as_view(), name='index'),
+ url(r'^create/$', CreateView.as_view(), name='create'),
+ url(r'^(?P<router_id>[^/]+)/$',
+ DetailView.as_view(),
+ name='detail'),
+ url(r'^(?P<router_id>[^/]+)/addinterface', AddInterfaceView.as_view(),
+ name='addinterface'),
+ url(r'^(?P<router_id>[^/]+)/setgateway',
+ SetGatewayView.as_view(),
+ name='setgateway'),
+)
diff --git a/openstack_dashboard/dashboards/admin/routers/views.py b/openstack_dashboard/dashboards/admin/routers/views.py
new file mode 100644
index 000000000..47cba3bcb
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/routers/views.py
@@ -0,0 +1,76 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
+#
+# 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.
+
+"""
+Views for managing Quantum Routers.
+"""
+
+import logging
+
+from django.core.urlresolvers import reverse_lazy
+from django.utils.translation import ugettext_lazy as _
+
+from horizon import exceptions
+from horizon import forms
+from openstack_dashboard import api
+from openstack_dashboard.dashboards.admin.networks import views
+from openstack_dashboard.dashboards.project.routers import views as r_views
+
+from .ports.tables import PortsTable
+from .forms import CreateForm
+from .tables import RoutersTable
+
+
+LOG = logging.getLogger(__name__)
+
+
+class IndexView(views.IndexView):
+ table_class = RoutersTable
+ template_name = 'admin/routers/index.html'
+
+ def _get_routers(self, search_opts=None):
+ try:
+ routers = api.quantum.router_list(self.request,
+ search_opts=search_opts)
+ except:
+ routers = []
+ exceptions.handle(self.request,
+ _('Unable to retrieve router list.'))
+ if routers:
+ tenant_dict = self._get_tenant_list()
+ for r in routers:
+ # Set tenant name
+ tenant = tenant_dict.get(r.tenant_id, None)
+ r.tenant_name = getattr(tenant, 'name', None)
+ # If name is empty use UUID as name
+ r.set_id_as_name_if_empty()
+ return routers
+
+ def get_data(self):
+ routers = self._get_routers()
+ return routers
+
+
+class DetailView(r_views.DetailView):
+ table_classes = (PortsTable, )
+ template_name = 'admin/routers/detail.html'
+ failure_url = reverse_lazy('horizon:admin:routers:index')
+
+
+class CreateView(forms.ModalFormView):
+ form_class = CreateForm
+ template_name = 'admin/routers/create.html'
+ success_url = reverse_lazy("horizon:admin:routers:index")
diff --git a/openstack_dashboard/dashboards/project/dashboard.py b/openstack_dashboard/dashboards/project/dashboard.py
index 691d7fa41..79df86a64 100644
--- a/openstack_dashboard/dashboards/project/dashboard.py
+++ b/openstack_dashboard/dashboards/project/dashboard.py
@@ -27,7 +27,8 @@ class BasePanels(horizon.PanelGroup):
'volumes',
'images_and_snapshots',
'access_and_security',
- 'networks')
+ 'networks',
+ 'routers')
class ObjectStorePanels(horizon.PanelGroup):
diff --git a/openstack_dashboard/dashboards/project/routers/__init__.py b/openstack_dashboard/dashboards/project/routers/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/routers/__init__.py
diff --git a/openstack_dashboard/dashboards/project/routers/forms.py b/openstack_dashboard/dashboards/project/routers/forms.py
new file mode 100644
index 000000000..ba45c5764
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/routers/forms.py
@@ -0,0 +1,41 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
+# All rights reserved.
+
+"""
+Views for managing Quantum Routers.
+"""
+import logging
+
+from django.core.urlresolvers import reverse
+from django.utils.translation import ugettext_lazy as _
+
+from horizon import forms
+from horizon import exceptions
+from horizon import messages
+from openstack_dashboard import api
+
+LOG = logging.getLogger(__name__)
+
+
+class CreateForm(forms.SelfHandlingForm):
+ name = forms.CharField(max_length="255", label=_("Router Name"))
+ failure_url = 'horizon:project:routers:index'
+
+ def __init__(self, request, *args, **kwargs):
+ super(CreateForm, self).__init__(request, *args, **kwargs)
+
+ def handle(self, request, data):
+ try:
+ router = api.quantum.router_create(request,
+ name=data['name'])
+ message = 'Router created "%s"' % data['name']
+ messages.success(request, message)
+ return router
+ except:
+ msg = _('Failed to create router "%s".') % data['name']
+ LOG.info(msg)
+ redirect = reverse(self.failure_url)
+ exceptions.handle(request, msg, redirect=redirect)
+ return False
diff --git a/openstack_dashboard/dashboards/project/routers/panel.py b/openstack_dashboard/dashboards/project/routers/panel.py
new file mode 100644
index 000000000..09218dd99
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/routers/panel.py
@@ -0,0 +1,29 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
+#
+# 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 django.utils.translation import ugettext_lazy as _
+
+import horizon
+
+from openstack_dashboard.dashboards.project import dashboard
+
+
+class Routers(horizon.Panel):
+ name = "Routers"
+ slug = 'routers'
+ permissions = ('openstack.services.network',)
+
+dashboard.Project.register(Routers)
diff --git a/openstack_dashboard/dashboards/project/routers/ports/__init__.py b/openstack_dashboard/dashboards/project/routers/ports/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/routers/ports/__init__.py
diff --git a/openstack_dashboard/dashboards/project/routers/ports/forms.py b/openstack_dashboard/dashboards/project/routers/ports/forms.py
new file mode 100644
index 000000000..7699f0b74
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/routers/ports/forms.py
@@ -0,0 +1,132 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
+#
+# 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.
+
+import logging
+
+from django.core.urlresolvers import reverse
+from django.utils.translation import ugettext_lazy as _
+
+from horizon import forms
+from horizon import messages
+from horizon import exceptions
+from openstack_dashboard import api
+
+LOG = logging.getLogger(__name__)
+
+
+class AddInterface(forms.SelfHandlingForm):
+ subnet_id = forms.ChoiceField(label=_("Subnet ID"), required=False)
+ router_id = forms.CharField(label=_("Router ID"),
+ widget=forms.TextInput(
+ attrs={'readonly': 'readonly'}))
+ failure_url = 'horizon:project:routers:detail'
+
+ def __init__(self, request, *args, **kwargs):
+ super(AddInterface, self).__init__(request, *args, **kwargs)
+ c = self.populate_subnet_id_choices(request)
+ self.fields['subnet_id'].choices = c
+
+ def populate_subnet_id_choices(self, request):
+ tenant_id = self.request.user.tenant_id
+ networks = []
+ try:
+ networks = api.quantum.network_list_for_tenant(request, tenant_id)
+ except Exception as e:
+ msg = _('Failed to get network list %s') % e.message
+ LOG.info(msg)
+ messages.error(request, msg)
+ redirect = reverse(self.failure_url,
+ args=[request.REQUEST['router_id']])
+ exceptions.handle(request, msg, redirect=redirect)
+ return
+
+ choices = []
+ for n in networks:
+ net_name = n.name + ': ' if n.name else ''
+ choices += [(subnet.id,
+ '%s%s (%s)' % (net_name, subnet.cidr,
+ subnet.name or subnet.id))
+ for subnet in n['subnets']]
+ if choices:
+ choices.insert(0, ("", _("Select Subnet")))
+ else:
+ choices.insert(0, ("", _("No subnets available.")))
+ return choices
+
+ def handle(self, request, data):
+ 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
+ except Exception as e:
+ msg = _('Failed to add_interface %s') % e.message
+ LOG.info(msg)
+ messages.error(request, msg)
+ redirect = reverse(self.failure_url, args=[data['router_id']])
+ exceptions.handle(request, msg, redirect=redirect)
+
+
+class SetGatewayForm(forms.SelfHandlingForm):
+ network_id = forms.ChoiceField(label=_("Network ID"), required=False)
+ router_id = forms.CharField(label=_("Router ID"),
+ widget=forms.TextInput(
+ attrs={'readonly': 'readonly'}))
+ failure_url = 'horizon:project:routers:detail'
+
+ 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
+
+ def populate_network_id_choices(self, request):
+ search_opts = {'router:external': True}
+ try:
+ networks = api.quantum.network_list(request, **search_opts)
+ except Exception as e:
+ msg = _('Failed to get network list %s') % e.message
+ LOG.info(msg)
+ messages.error(request, msg)
+ redirect = reverse(self.failure_url,
+ args=[request.REQUEST['router_id']])
+ exceptions.handle(request, msg, redirect=redirect)
+ return
+ choices = [(network.id, network.name or network.id)
+ for network in networks]
+ if choices:
+ choices.insert(0, ("", _("Select network")))
+ else:
+ choices.insert(0, ("", _("No networks available.")))
+ return choices
+
+ def handle(self, request, data):
+ try:
+ api.quantum.router_add_gateway(request,
+ data['router_id'],
+ data['network_id'])
+ msg = _('Gateway interface is added')
+ LOG.debug(msg)
+ messages.success(request, msg)
+ return True
+ except Exception as e:
+ msg = _('Failed to set gateway %s') % e.message
+ LOG.info(msg)
+ messages.error(request, msg)
+ redirect = reverse(self.failure_url, args=[data['router_id']])
+ 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
new file mode 100644
index 000000000..9f1b5c85e
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/routers/ports/tables.py
@@ -0,0 +1,103 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
+#
+# 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.
+
+import logging
+
+from django.core.urlresolvers import reverse
+from django.utils.translation import ugettext_lazy as _
+
+from horizon import exceptions
+from horizon import tables
+from openstack_dashboard import api
+from openstack_dashboard.dashboards.project.networks.ports.tables import\
+ get_fixed_ips, get_attached
+
+LOG = logging.getLogger(__name__)
+
+
+def get_device_owner(port):
+ if port['device_owner'] == 'network:router_gateway':
+ return _('Gateway')
+ else:
+ return ' '
+
+
+class SetGateway(tables.LinkAction):
+ name = "setgateway"
+ verbose_name = _("Add Gateway Interface")
+ url = "horizon:project:routers:setgateway"
+ classes = ("ajax-modal", "btn-camera")
+
+ def get_link_url(self, datum=None):
+ router_id = self.table.kwargs['router_id']
+ return reverse(self.url, args=(router_id,))
+
+
+class AddInterface(tables.LinkAction):
+ name = "create"
+ verbose_name = _("Add Interface")
+ url = "horizon:project:routers:addinterface"
+ classes = ("ajax-modal", "btn-create")
+
+ def get_link_url(self, datum=None):
+ router_id = self.table.kwargs['router_id']
+ return reverse(self.url, args=(router_id,))
+
+
+class RemoveInterface(tables.DeleteAction):
+ data_type_singular = _("Interface")
+ data_type_plural = _("Interfaces")
+ failure_url = 'horizon:project:routers:detail'
+
+ def delete(self, request, obj_id):
+ try:
+ router_id = self.table.kwargs['router_id']
+ port = api.quantum.port_get(request, obj_id)
+ if port['device_owner'] == 'network:router_gateway':
+ api.quantum.router_remove_gateway(request, router_id)
+ else:
+ api.quantum.router_remove_interface(request,
+ router_id,
+ port_id=obj_id)
+ except:
+ msg = _('Failed to delete interface %s') % obj_id
+ LOG.info(msg)
+ router_id = self.table.kwargs['router_id']
+ redirect = reverse(self.failure_url,
+ args=[router_id])
+ exceptions.handle(request, msg, redirect=redirect)
+
+
+class PortsTable(tables.DataTable):
+ name = tables.Column("name",
+ verbose_name=_("Name"),
+ link="horizon:project:networks:ports:detail")
+ fixed_ips = tables.Column(get_fixed_ips, verbose_name=_("Fixed IPs"))
+ attached = tables.Column(get_attached, verbose_name=_("Device Attached"))
+ status = tables.Column("status", verbose_name=_("Status"))
+ device_owner = tables.Column(get_device_owner,
+ verbose_name=_("Type"))
+ admin_state = tables.Column("admin_state",
+ verbose_name=_("Admin State"))
+
+ def get_object_display(self, port):
+ return port.id
+
+ class Meta:
+ name = "interfaces"
+ verbose_name = _("Interfaces")
+ table_actions = (AddInterface, SetGateway, RemoveInterface)
+ row_actions = (RemoveInterface, )
diff --git a/openstack_dashboard/dashboards/project/routers/ports/tabs.py b/openstack_dashboard/dashboards/project/routers/ports/tabs.py
new file mode 100644
index 000000000..4dc056876
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/routers/ports/tabs.py
@@ -0,0 +1,47 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
+#
+# 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.
+import logging
+
+from django.core.urlresolvers import reverse
+from django.utils.translation import ugettext_lazy as _
+
+from horizon import exceptions
+from horizon import tabs
+from openstack_dashboard import api
+
+LOG = logging.getLogger(__name__)
+
+
+class OverviewTab(tabs.Tab):
+ name = _("Overview")
+ slug = "overview"
+ template_name = "project/networks/ports/_detail_overview.html"
+ failure_url = 'horizon:project:routers:index'
+
+ def get_context_data(self, request):
+ port_id = self.tab_group.kwargs['port_id']
+ try:
+ port = api.quantum.port_get(self.request, port_id)
+ except:
+ redirect = reverse(self.failure_url)
+ msg = _('Unable to retrieve port details.')
+ exceptions.handle(request, msg, redirect=redirect)
+ return {'port': port}
+
+
+class PortDetailTabs(tabs.TabGroup):
+ slug = "port_details"
+ tabs = (OverviewTab,)
diff --git a/openstack_dashboard/dashboards/project/routers/ports/urls.py b/openstack_dashboard/dashboards/project/routers/ports/urls.py
new file mode 100644
index 000000000..fa192c7de
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/routers/ports/urls.py
@@ -0,0 +1,24 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
+#
+# 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 django.conf.urls.defaults import patterns, url
+
+from .views import DetailView
+
+PORTS = r'^(?P<port_id>[^/]+)/%s$'
+
+urlpatterns = patterns('horizon.dashboards.project.networks.ports.views',
+ url(PORTS % 'detail', DetailView.as_view(), name='detail'))
diff --git a/openstack_dashboard/dashboards/project/routers/ports/views.py b/openstack_dashboard/dashboards/project/routers/ports/views.py
new file mode 100644
index 000000000..eab18efef
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/routers/ports/views.py
@@ -0,0 +1,100 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
+#
+# 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.
+
+import logging
+
+from django.core.urlresolvers import reverse
+
+from horizon import tabs
+from horizon import forms
+from horizon import exceptions
+from openstack_dashboard import api
+from .tabs import PortDetailTabs
+from .forms import (AddInterface, SetGatewayForm)
+
+
+LOG = logging.getLogger(__name__)
+
+
+class AddInterfaceView(forms.ModalFormView):
+ form_class = AddInterface
+ template_name = 'project/routers/ports/create.html'
+ success_url = 'horizon:project:routers:detail'
+ failure_url = 'horizon:project:routers:detail'
+
+ def get_success_url(self):
+ return reverse(self.success_url,
+ args=(self.kwargs['router_id'],))
+
+ def get_object(self):
+ if not hasattr(self, "_object"):
+ try:
+ router_id = self.kwargs["router_id"]
+ self._object = api.quantum.router_get(self.request,
+ router_id)
+ except:
+ redirect = reverse(self.failure_url, args=[router_id])
+ msg = _("Unable to retrieve router.")
+ exceptions.handle(self.request, msg, redirect=redirect)
+ return self._object
+
+ def get_context_data(self, **kwargs):
+ context = super(AddInterfaceView, self).get_context_data(**kwargs)
+ context['router'] = self.get_object()
+ return context
+
+ def get_initial(self):
+ router = self.get_object()
+ return {"router_id": self.kwargs['router_id'],
+ "router_name": router.name}
+
+
+class SetGatewayView(forms.ModalFormView):
+ form_class = SetGatewayForm
+ template_name = 'project/routers/ports/setgateway.html'
+ success_url = 'horizon:project:routers:detail'
+ failure_url = 'horizon:project:routers:detail'
+
+ def get_success_url(self):
+ return reverse(self.success_url,
+ args=(self.kwargs['router_id'],))
+
+ def get_object(self):
+ if not hasattr(self, "_object"):
+ try:
+ router_id = self.kwargs["router_id"]
+ self._object = api.quantum.router_get(self.request,
+ router_id)
+ except:
+ redirect = reverse(self.failure_url)
+ msg = _("Unable to set gateway.")
+ exceptions.handle(self.request, msg, redirect=redirect)
+ return self._object
+
+ def get_context_data(self, **kwargs):
+ context = super(SetGatewayView, self).get_context_data(**kwargs)
+ context['router'] = self.get_object()
+ return context
+
+ def get_initial(self):
+ router = self.get_object()
+ return {"router_id": self.kwargs['router_id'],
+ "router_name": router.name}
+
+
+class DetailView(tabs.TabView):
+ tab_group_class = PortDetailTabs
+ template_name = 'project/networks/ports/detail.html'
diff --git a/openstack_dashboard/dashboards/project/routers/tables.py b/openstack_dashboard/dashboards/project/routers/tables.py
new file mode 100644
index 000000000..e643a74a2
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/routers/tables.py
@@ -0,0 +1,90 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
+#
+# 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.
+
+import logging
+
+from django.core.urlresolvers import reverse
+from django.template.defaultfilters import title
+from django.utils.translation import ugettext_lazy as _
+
+from horizon import exceptions
+from horizon import messages
+from horizon import tables
+from openstack_dashboard import api
+from quantumclient.common import exceptions as q_ext
+
+LOG = logging.getLogger(__name__)
+
+
+class DeleteRouter(tables.DeleteAction):
+ data_type_singular = _("Router")
+ data_type_plural = _("Routers")
+ redirect_url = "horizon:project:routers:index"
+
+ def delete(self, request, obj_id):
+ obj = self.table.get_object_by_id(obj_id)
+ name = self.table.get_object_display(obj)
+ try:
+ api.router_delete(request, obj_id)
+ except q_ext.QuantumClientException as e:
+ msg = _('Unable to delete router "%s"') % e.message
+ LOG.info(msg)
+ messages.error(request, msg)
+ redirect = reverse(self.redirect_url)
+ raise exceptions.Http302(redirect, message=msg)
+ except Exception as e:
+ msg = _('Unable to delete router "%s"') % name
+ LOG.info(msg)
+ exceptions.handle(request, msg)
+
+ def allowed(self, request, router=None):
+ return True
+
+
+class CreateRouter(tables.LinkAction):
+ name = "create"
+ verbose_name = _("Create Router")
+ url = "horizon:project:routers:create"
+ classes = ("ajax-modal", "btn-create")
+
+
+class UpdateRow(tables.Row):
+ ajax = True
+
+ def get_data(self, request, router_id):
+ router = api.router_get(request, router_id)
+ return router
+
+
+class RoutersTable(tables.DataTable):
+ name = tables.Column("name",
+ verbose_name=_("Name"),
+ link="horizon:project:routers:detail")
+ status = tables.Column("status",
+ filters=(title,),
+ verbose_name=_("Status"),
+ status=True)
+
+ def get_object_display(self, obj):
+ return obj.name
+
+ class Meta:
+ name = "Routers"
+ verbose_name = _("Routers")
+ status_columns = ["status"]
+ row_class = UpdateRow
+ table_actions = (CreateRouter, DeleteRouter)
+ row_actions = (DeleteRouter, )
diff --git a/openstack_dashboard/dashboards/project/routers/tabs.py b/openstack_dashboard/dashboards/project/routers/tabs.py
new file mode 100644
index 000000000..0cc7eb274
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/routers/tabs.py
@@ -0,0 +1,45 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
+#
+# 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 django.core.urlresolvers import reverse
+from django.utils.translation import ugettext_lazy as _
+
+from horizon import exceptions
+from horizon import tabs
+from openstack_dashboard import api
+
+
+class OverviewTab(tabs.Tab):
+ name = _("Overview")
+ slug = "overview"
+ template_name = ("project/routers/_detail_overview.html")
+ redirect_url = 'horizon:project:routers:index'
+
+ def get_context_data(self, request):
+ router_id = self.tab_group.kwargs['router_id']
+ try:
+ router = api.router_get(request, router_id)
+ except:
+ redirect = reverse(redirect_url)
+ exceptions.handle(self.request,
+ _('Unable to retrieve router details.'),
+ redirect=redirect)
+ return {'router': router}
+
+
+class RouterDetailTabs(tabs.TabGroup):
+ slug = "router_details"
+ tabs = (OverviewTab,)
diff --git a/openstack_dashboard/dashboards/project/routers/templates/routers/_create.html b/openstack_dashboard/dashboards/project/routers/templates/routers/_create.html
new file mode 100644
index 000000000..6ca89b23d
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/routers/templates/routers/_create.html
@@ -0,0 +1,21 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n horizon humanize %}
+
+{% block form_id %}{% endblock %}
+{% block form_action %}{% url horizon:project:routers:create %}?{{ request.GET.urlencode }}{% endblock %}
+
+{% block modal_id %}create_router_modal{% endblock %}
+{% block modal-header %}{% trans "Create router" %}{% endblock %}
+
+{% block modal-body %}
+ <div class="left">
+ <fieldset>
+ {% include "horizon/common/_form_fields.html" %}
+ </fieldset>
+ </div>
+{% endblock %}
+
+{% block modal-footer %}
+ <input class="btn btn-primary pull-right" type="submit" value="{% trans "Create router" %}" />
+ <a href="{% url horizon:project:routers:index %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/routers/templates/routers/_detail_overview.html b/openstack_dashboard/dashboards/project/routers/templates/routers/_detail_overview.html
new file mode 100644
index 000000000..cbead2aa6
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/routers/templates/routers/_detail_overview.html
@@ -0,0 +1,15 @@
+{% load i18n sizeformat parse_date %}
+
+<h3>{% trans "Router Overview" %}: {{router.name|default:"None" }}</h3>
+
+<div class="info detail">
+ <dl>
+ <dt>{% trans "Name" %}</dt>
+ <dd>{{ router.name|default:"None" }}</dd>
+ <dt>{% trans "ID" %}</dt>
+ <dd>{{ router.id|default:"None" }}</dd>
+ <dt>{% trans "Status" %}</dt>
+ <dd>{{ router.status|default:"Unknown" }}</dd>
+ </dl>
+</div>
+
diff --git a/openstack_dashboard/dashboards/project/routers/templates/routers/create.html b/openstack_dashboard/dashboards/project/routers/templates/routers/create.html
new file mode 100644
index 000000000..3dfff361b
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/routers/templates/routers/create.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}Create router{% endblock %}
+
+{% block page_header %}
+ {% include "horizon/common/_page_header.html" with title=_("Create a router") %}
+{% endblock page_header %}
+
+{% block main %}
+ {% include 'project/routers/_create.html' %}
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/routers/templates/routers/detail.html b/openstack_dashboard/dashboards/project/routers/templates/routers/detail.html
new file mode 100644
index 000000000..ae7d15943
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/routers/templates/routers/detail.html
@@ -0,0 +1,15 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Router Details" %}{% endblock %}
+
+{% block page_header %}
+ {% include "horizon/common/_page_header.html" with title=_("Router Detail") %}
+{% endblock page_header %}
+
+{% block main %}
+{% include "project/routers/_detail_overview.html" %}
+<hr>
+<div id="interfaces">
+ {{ interfaces_table.render }}
+</div>
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/routers/templates/routers/index.html b/openstack_dashboard/dashboards/project/routers/templates/routers/index.html
new file mode 100644
index 000000000..b169f34c2
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/routers/templates/routers/index.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "routers" %}{% endblock %}
+
+{% block page_header %}
+ {% include "horizon/common/_page_header.html" with title=_("routers") %}
+{% endblock page_header %}
+
+{% block main %}
+ {{ table.render }}
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/routers/templates/routers/ports/_create.html b/openstack_dashboard/dashboards/project/routers/templates/routers/ports/_create.html
new file mode 100644
index 000000000..60b2269c4
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/routers/templates/routers/ports/_create.html
@@ -0,0 +1,25 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n %}
+
+{% block form_id %}add_interface_form{% endblock %}
+{% block form_action %}{% url horizon:project:routers:addinterface router.id %}
+{% endblock %}
+
+{% block modal-header %}{% trans "Add interface" %}{% endblock %}
+
+{% block modal-body %}
+<div class="left">
+ <fieldset>
+ {% include "horizon/common/_form_fields.html" %}
+ </fieldset>
+</div>
+<div class="right">
+ <h3>{% trans "Description" %}:</h3>
+ <p>{% trans "You can add interface to the network with subnet_id." %}</p>
+</div>
+{% endblock %}
+
+{% block modal-footer %}
+ <input class="btn btn-primary pull-right" type="submit" value="{% trans "Add interface" %}" />
+ <a href="{% url horizon:project:routers:index %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/routers/templates/routers/ports/_setgateway.html b/openstack_dashboard/dashboards/project/routers/templates/routers/ports/_setgateway.html
new file mode 100644
index 000000000..558507ba3
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/routers/templates/routers/ports/_setgateway.html
@@ -0,0 +1,25 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n %}
+
+{% block form_id %}setgateway_form{% endblock %}
+{% block form_action %}{% url horizon:project:routers:setgateway router.id %}
+{% endblock %}
+
+{% block modal-header %}{% trans "Add Gateway Interface" %}{% endblock %}
+
+{% block modal-body %}
+<div class="left">
+ <fieldset>
+ {% include "horizon/common/_form_fields.html" %}
+ </fieldset>
+</div>
+<div class="right">
+ <h3>{% trans "Description" %}:</h3>
+ <p>{% trans "You can add inteface for Gateway. In this interface, NAT rule of floating IP will be set." %}</p>
+</div>
+{% endblock %}
+
+{% block modal-footer %}
+ <input class="btn btn-primary pull-right" type="submit" value="{% trans "Set Gateway" %}" />
+ <a href="{% url horizon:project:routers:index %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/routers/templates/routers/ports/create.html b/openstack_dashboard/dashboards/project/routers/templates/routers/ports/create.html
new file mode 100644
index 000000000..300b8d152
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/routers/templates/routers/ports/create.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Add Interface" %}{% endblock %}
+
+{% block page_header %}
+ {% include "horizon/common/_page_header.html" with title=_("Add Interface") %}
+{% endblock page_header %}
+
+{% block main %}
+ {% include "project/routers/ports/_create.html" %}
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/routers/templates/routers/ports/setgateway.html b/openstack_dashboard/dashboards/project/routers/templates/routers/ports/setgateway.html
new file mode 100644
index 000000000..5c966b88d
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/routers/templates/routers/ports/setgateway.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Set Gateway" %}{% endblock %}
+
+{% block page_header %}
+ {% include "horizon/common/_page_header.html" with title=_("Set Gateway") %}
+{% endblock page_header %}
+
+{% block main %}
+ {% include "project/routers/ports/_setgateway.html" %}
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/routers/tests.py b/openstack_dashboard/dashboards/project/routers/tests.py
new file mode 100644
index 000000000..75c17c3e6
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/routers/tests.py
@@ -0,0 +1,235 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
+#
+# 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 mox import IsA
+from django import http
+from django.core.urlresolvers import reverse
+
+from openstack_dashboard import api
+from openstack_dashboard.test import helpers as test
+
+
+class RouterTests(test.TestCase):
+ DASHBOARD = 'project'
+ INDEX_URL = reverse('horizon:%s:routers:index' % DASHBOARD)
+ DETAIL_PATH = 'horizon:%s:routers:detail' % DASHBOARD
+
+ @test.create_stubs({api.quantum: ('router_list',)})
+ def test_index(self):
+ api.quantum.router_list(
+ IsA(http.HttpRequest),
+ tenant_id=self.tenant.id,
+ search_opts=None).AndReturn(self.routers.list())
+
+ self.mox.ReplayAll()
+
+ res = self.client.get(self.INDEX_URL)
+
+ self.assertTemplateUsed(res, '%s/routers/index.html' % self.DASHBOARD)
+ routers = res.context['table'].data
+ self.assertItemsEqual(routers, self.routers.list())
+
+ @test.create_stubs({api.quantum: ('router_list',)})
+ def test_index_router_list_exception(self):
+ api.quantum.router_list(
+ IsA(http.HttpRequest),
+ tenant_id=self.tenant.id,
+ search_opts=None).AndRaise(self.exceptions.quantum)
+
+ self.mox.ReplayAll()
+
+ res = self.client.get(self.INDEX_URL)
+
+ self.assertTemplateUsed(res, '%s/routers/index.html' % self.DASHBOARD)
+ self.assertEqual(len(res.context['table'].data), 0)
+ self.assertMessageCount(res, error=1)
+
+ @test.create_stubs({api.quantum: ('router_get', 'port_list')})
+ def test_router_detail(self):
+ router_id = self.routers.first().id
+ api.quantum.router_get(IsA(http.HttpRequest), router_id)\
+ .AndReturn(self.routers.first())
+ api.quantum.port_list(IsA(http.HttpRequest),
+ device_id=router_id)\
+ .AndReturn([self.ports.first()])
+
+ self.mox.ReplayAll()
+
+ res = self.client.get(reverse('horizon:%s'
+ ':routers:detail' % self.DASHBOARD,
+ args=[router_id]))
+
+ self.assertTemplateUsed(res, '%s/routers/detail.html' % self.DASHBOARD)
+ ports = res.context['interfaces_table'].data
+ self.assertItemsEqual(ports, [self.ports.first()])
+
+ @test.create_stubs({api.quantum: ('router_get', 'port_list')})
+ def test_router_detail_exception(self):
+ router_id = self.routers.first().id
+ api.quantum.router_get(IsA(http.HttpRequest), router_id)\
+ .AndRaise(self.exceptions.quantum)
+ api.quantum.port_list(IsA(http.HttpRequest),
+ device_id=router_id)\
+ .AndReturn([self.ports.first()])
+ self.mox.ReplayAll()
+
+ res = self.client.get(reverse('horizon:%s'
+ ':routers:detail' % self.DASHBOARD,
+ args=[router_id]))
+ self.assertRedirectsNoFollow(res, self.INDEX_URL)
+
+ @test.create_stubs({api.quantum: ('router_create',)})
+ def test_router_create_post(self):
+ router = self.routers.first()
+ api.quantum.router_create(IsA(http.HttpRequest), name=router.name)\
+ .AndReturn(router)
+ self.mox.ReplayAll()
+
+ form_data = {'name': router.name}
+ url = reverse('horizon:%s:routers:create' % self.DASHBOARD)
+ res = self.client.post(url, form_data)
+
+ self.assertNoFormErrors(res)
+ self.assertRedirectsNoFollow(res, self.INDEX_URL)
+
+ @test.create_stubs({api.quantum: ('router_create',)})
+ def test_router_create_post_exception(self):
+ router = self.routers.first()
+ api.quantum.router_create(IsA(http.HttpRequest), name=router.name)\
+ .AndRaise(self.exceptions.quantum)
+ self.mox.ReplayAll()
+
+ form_data = {'name': router.name}
+ url = reverse('horizon:%s:routers:create' % self.DASHBOARD)
+ res = self.client.post(url, form_data)
+
+ self.assertNoFormErrors(res)
+ self.assertRedirectsNoFollow(res, self.INDEX_URL)
+
+ @test.create_stubs({api.quantum: ('router_get',
+ 'router_add_interface',
+ 'network_list')})
+ def test_router_addinterface(self):
+ router = self.routers.first()
+ subnet = self.subnets.first()
+ api.quantum.router_add_interface(
+ IsA(http.HttpRequest),
+ router.id,
+ subnet_id=subnet.id).AndReturn(None)
+ api.quantum.router_get(IsA(http.HttpRequest), router.id)\
+ .AndReturn(router)
+ api.quantum.network_list(
+ IsA(http.HttpRequest),
+ shared=False,
+ tenant_id=router['tenant_id']).AndReturn(self.networks.list())
+ api.quantum.network_list(
+ IsA(http.HttpRequest),
+ shared=True).AndReturn([])
+ self.mox.ReplayAll()
+
+ form_data = {'router_id': router.id,
+ 'subnet_id': subnet.id}
+
+ url = reverse('horizon:%s:routers:addinterface' % self.DASHBOARD,
+ args=[router.id])
+ res = self.client.post(url, form_data)
+ self.assertNoFormErrors(res)
+ detail_url = reverse(self.DETAIL_PATH, args=[router.id])
+ self.assertRedirectsNoFollow(res, detail_url)
+
+ @test.create_stubs({api.quantum: ('router_get',
+ 'router_add_interface',
+ 'network_list')})
+ def test_router_addinterface_exception(self):
+ router = self.routers.first()
+ subnet = self.subnets.first()
+ api.quantum.router_add_interface(
+ IsA(http.HttpRequest),
+ router.id,
+ subnet_id=subnet.id).AndRaise(self.exceptions.quantum)
+ api.quantum.router_get(IsA(http.HttpRequest), router.id)\
+ .AndReturn(router)
+ api.quantum.network_list(
+ IsA(http.HttpRequest),
+ shared=False,
+ tenant_id=router['tenant_id']).AndReturn(self.networks.list())
+ api.quantum.network_list(
+ IsA(http.HttpRequest),
+ shared=True).AndReturn([])
+ self.mox.ReplayAll()
+
+ form_data = {'router_id': router.id,
+ 'subnet_id': subnet.id}
+
+ url = reverse('horizon:%s:routers:addinterface' % self.DASHBOARD,
+ args=[router.id])
+ res = self.client.post(url, form_data)
+ self.assertNoFormErrors(res)
+ detail_url = reverse(self.DETAIL_PATH, args=[router.id])
+ self.assertRedirectsNoFollow(res, detail_url)
+
+ @test.create_stubs({api.quantum: ('router_get',
+ 'router_add_gateway',
+ 'network_list')})
+ def test_router_add_gateway(self):
+ router = self.routers.first()
+ network = self.networks.first()
+ api.quantum.router_add_gateway(
+ IsA(http.HttpRequest),
+ router.id,
+ network.id).AndReturn(None)
+ api.quantum.router_get(
+ IsA(http.HttpRequest), router.id).AndReturn(router)
+ search_opts = {'router:external': True}
+ api.quantum.network_list(
+ IsA(http.HttpRequest), **search_opts).AndReturn([network])
+ self.mox.ReplayAll()
+
+ form_data = {'router_id': router.id,
+ 'network_id': network.id}
+
+ url = reverse('horizon:%s:routers:setgateway' % self.DASHBOARD,
+ args=[router.id])
+ res = self.client.post(url, form_data)
+ self.assertNoFormErrors(res)
+ detail_url = reverse(self.DETAIL_PATH, args=[router.id])
+ self.assertRedirectsNoFollow(res, detail_url)
+
+ @test.create_stubs({api.quantum: ('router_get',
+ 'router_add_gateway',
+ 'network_list')})
+ def test_router_add_gateway_exception(self):
+ router = self.routers.first()
+ network = self.networks.first()
+ api.quantum.router_add_gateway(
+ IsA(http.HttpRequest),
+ router.id,
+ network.id).AndRaise(self.exceptions.quantum)
+ api.quantum.router_get(
+ IsA(http.HttpRequest), router.id).AndReturn(router)
+ search_opts = {'router:external': True}
+ api.quantum.network_list(
+ IsA(http.HttpRequest), **search_opts).AndReturn([network])
+ self.mox.ReplayAll()
+
+ form_data = {'router_id': router.id,
+ 'network_id': network.id}
+
+ url = reverse('horizon:%s:routers:setgateway' % self.DASHBOARD,
+ args=[router.id])
+ res = self.client.post(url, form_data)
+ self.assertNoFormErrors(res)
+ detail_url = reverse(self.DETAIL_PATH, args=[router.id])
+ self.assertRedirectsNoFollow(res, detail_url)
diff --git a/openstack_dashboard/dashboards/project/routers/urls.py b/openstack_dashboard/dashboards/project/routers/urls.py
new file mode 100644
index 000000000..ea1c156c1
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/routers/urls.py
@@ -0,0 +1,34 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
+#
+# 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 django.conf.urls.defaults import patterns, url
+
+from .views import (IndexView, CreateView, DetailView)
+from .ports.views import (AddInterfaceView, SetGatewayView)
+
+
+urlpatterns = patterns('horizon.dashboards.project.routers.views',
+ url(r'^$', IndexView.as_view(), name='index'),
+ url(r'^create/$', CreateView.as_view(), name='create'),
+ url(r'^(?P<router_id>[^/]+)/$',
+ DetailView.as_view(),
+ name='detail'),
+ url(r'^(?P<router_id>[^/]+)/addinterface', AddInterfaceView.as_view(),
+ name='addinterface'),
+ url(r'^(?P<router_id>[^/]+)/setgateway',
+ SetGatewayView.as_view(),
+ name='setgateway'),
+)
diff --git a/openstack_dashboard/dashboards/project/routers/views.py b/openstack_dashboard/dashboards/project/routers/views.py
new file mode 100644
index 000000000..e051037fa
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/routers/views.py
@@ -0,0 +1,105 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
+#
+# 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.
+
+"""
+Views for managing Quantum Routers.
+"""
+
+import logging
+
+from django import shortcuts
+from django.core.urlresolvers import reverse_lazy
+from django.utils.translation import ugettext_lazy as _
+from django.utils.datastructures import SortedDict
+
+from horizon import exceptions
+from horizon import forms
+from horizon import tables
+from horizon import tabs
+from openstack_dashboard import api
+from .ports.tables import PortsTable
+from .forms import CreateForm
+from .tables import RoutersTable
+from .tabs import RouterDetailTabs
+
+
+LOG = logging.getLogger(__name__)
+
+
+class IndexView(tables.DataTableView):
+ table_class = RoutersTable
+ template_name = 'project/routers/index.html'
+
+ def _get_routers(self, search_opts=None):
+ try:
+ tenant_id = self.request.user.tenant_id
+ routers = api.quantum.router_list(self.request,
+ tenant_id=tenant_id,
+ search_opts=search_opts)
+ except:
+ routers = []
+ exceptions.handle(self.request,
+ _('Unable to retrieve router list.'))
+ for r in routers:
+ r.set_id_as_name_if_empty()
+ return routers
+
+ def get_data(self):
+ routers = self._get_routers()
+ return routers
+
+
+class DetailView(tables.MultiTableView):
+ table_classes = (PortsTable, )
+ template_name = 'project/routers/detail.html'
+ failure_url = reverse_lazy('horizon:project:routers:index')
+
+ def _get_data(self):
+ if not hasattr(self, "_router"):
+ try:
+ router_id = self.kwargs['router_id']
+ router = api.quantum.router_get(self.request, router_id)
+ router.set_id_as_name_if_empty(length=0)
+ except:
+ msg = _('Unable to retrieve details for router "%s".') \
+ % (router_id)
+ exceptions.handle(self.request, msg, redirect=self.failure_url)
+ self._router = router
+ return self._router
+
+ def get_context_data(self, **kwargs):
+ context = super(DetailView, self).get_context_data(**kwargs)
+ context["router"] = self._get_data()
+ return context
+
+ def get_interfaces_data(self):
+ try:
+ device_id = self.kwargs['router_id']
+ ports = api.quantum.port_list(self.request,
+ device_id=device_id)
+ except:
+ ports = []
+ msg = _('Port list can not be retrieved.')
+ exceptions.handle(self.request, msg)
+ for p in ports:
+ p.set_id_as_name_if_empty()
+ return ports
+
+
+class CreateView(forms.ModalFormView):
+ form_class = CreateForm
+ template_name = 'project/routers/create.html'
+ success_url = reverse_lazy("horizon:project:routers:index")
diff --git a/openstack_dashboard/test/api_tests/quantum_tests.py b/openstack_dashboard/test/api_tests/quantum_tests.py
index 22bf459c8..49743a20a 100644
--- a/openstack_dashboard/test/api_tests/quantum_tests.py
+++ b/openstack_dashboard/test/api_tests/quantum_tests.py
@@ -203,3 +203,70 @@ class QuantumApiTests(test.APITestCase):
self.mox.ReplayAll()
api.quantum.port_delete(self.request, port_id)
+
+ def test_router_list(self):
+ routers = {'routers': self.api_routers.list()}
+
+ quantumclient = self.stub_quantumclient()
+ quantumclient.list_routers().AndReturn(routers)
+ self.mox.ReplayAll()
+
+ ret_val = api.quantum.router_list(self.request)
+ for n in ret_val:
+ self.assertIsInstance(n, api.quantum.Router)
+
+ def test_router_get(self):
+ router = {'router': self.api_routers.first()}
+ router_id = self.api_routers.first()['id']
+
+ quantumclient = self.stub_quantumclient()
+ quantumclient.show_router(router_id).AndReturn(router)
+ self.mox.ReplayAll()
+
+ ret_val = api.quantum.router_get(self.request, router_id)
+ self.assertIsInstance(ret_val, api.quantum.Router)
+
+ def test_router_create(self):
+ router = {'router': self.api_routers.first()}
+
+ quantumclient = self.stub_quantumclient()
+ form_data = {'router': {'name': 'router1'}}
+ quantumclient.create_router(body=form_data).AndReturn(router)
+ self.mox.ReplayAll()
+
+ ret_val = api.quantum.router_create(self.request, name='router1')
+ self.assertIsInstance(ret_val, api.quantum.Router)
+
+ def test_router_delete(self):
+ router_id = self.api_routers.first()['id']
+
+ quantumclient = self.stub_quantumclient()
+ quantumclient.delete_router(router_id)
+ self.mox.ReplayAll()
+
+ api.quantum.router_delete(self.request, router_id)
+
+ def test_router_add_interface(self):
+ subnet_id = self.api_subnets.first()['id']
+ router_id = self.api_routers.first()['id']
+
+ quantumclient = self.stub_quantumclient()
+ form_data = {'subnet_id': subnet_id}
+ quantumclient.add_interface_router(
+ router_id, form_data).AndReturn(None)
+ self.mox.ReplayAll()
+
+ api.quantum.router_add_interface(
+ self.request, router_id, subnet_id=subnet_id)
+
+ def test_router_remove_interface(self):
+ router_id = self.api_routers.first()['id']
+ fake_port = self.api_ports.first()['id']
+
+ quantumclient = self.stub_quantumclient()
+ quantumclient.remove_interface_router(
+ router_id, {'port_id': fake_port})
+ self.mox.ReplayAll()
+
+ api.quantum.router_remove_interface(
+ self.request, router_id, port_id=fake_port)
diff --git a/openstack_dashboard/test/test_data/quantum_data.py b/openstack_dashboard/test/test_data/quantum_data.py
index 23e138127..7c60323fa 100644
--- a/openstack_dashboard/test/test_data/quantum_data.py
+++ b/openstack_dashboard/test/test_data/quantum_data.py
@@ -14,7 +14,7 @@
import copy
-from openstack_dashboard.api.quantum import Network, Subnet, Port
+from openstack_dashboard.api.quantum import Network, Subnet, Port, Router
from .utils import TestDataContainer
@@ -24,11 +24,13 @@ def data(TEST):
TEST.networks = TestDataContainer()
TEST.subnets = TestDataContainer()
TEST.ports = TestDataContainer()
+ TEST.routers = TestDataContainer()
# data return by quantumclient
TEST.api_networks = TestDataContainer()
TEST.api_subnets = TestDataContainer()
TEST.api_ports = TestDataContainer()
+ TEST.api_routers = TestDataContainer()
# 1st network
network_dict = {'admin_state_up': True,
@@ -62,7 +64,6 @@ def data(TEST):
'network_id': network_dict['id'],
'status': 'ACTIVE',
'tenant_id': network_dict['tenant_id']}
-
TEST.api_networks.add(network_dict)
TEST.api_subnets.add(subnet_dict)
TEST.api_ports.add(port_dict)
@@ -109,7 +110,6 @@ def data(TEST):
'network_id': network_dict['id'],
'status': 'ACTIVE',
'tenant_id': network_dict['tenant_id']}
-
TEST.api_networks.add(network_dict)
TEST.api_subnets.add(subnet_dict)
TEST.api_ports.add(port_dict)
@@ -120,3 +120,29 @@ def data(TEST):
TEST.networks.add(Network(network))
TEST.subnets.add(subnet)
TEST.ports.add(Port(port_dict))
+
+ # Set up router data
+ port_dict = {'admin_state_up': True,
+ 'device_id': '7180cede-bcd8-4334-b19f-f7ef2f331f53',
+ 'device_owner': 'network:router_gateway',
+ 'fixed_ips': [{'ip_address': '10.0.0.3',
+ 'subnet_id': subnet_dict['id']}],
+ 'id': '3ec7f3db-cb2f-4a34-ab6b-69a64d3f008c',
+ 'mac_address': 'fa:16:3e:9c:d5:7e',
+ 'name': '',
+ 'network_id': network_dict['id'],
+ 'status': 'ACTIVE',
+ 'tenant_id': '1'}
+ TEST.api_ports.add(port_dict)
+ TEST.ports.add(Port(port_dict))
+
+ router_dict = {'id': '279989f7-54bb-41d9-ba42-0d61f12fda61',
+ 'name': 'router1',
+ 'tenant_id': '1'}
+ TEST.api_routers.add(router_dict)
+ TEST.routers.add(Router(router_dict))
+ router_dict = {'id': '279989f7-54bb-41d9-ba42-0d61f12fda61',
+ 'name': 'router1',
+ 'tenant_id': '1'}
+ TEST.api_routers.add(router_dict)
+ TEST.routers.add(Router(router_dict))