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
This commit is contained in:
Nachi Ueno 2012-09-20 07:10:06 +00:00
parent 31d55e503d
commit eed092a5bb
47 changed files with 1968 additions and 6 deletions

View File

@ -78,6 +78,15 @@ class Port(QuantumAPIDictWrapper):
super(Port, self).__init__(apiresource) 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'} 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]) subnet_dict = SortedDict([(s['id'], s) for s in subnets])
# Expand subnet list from subnet_id to values. # Expand subnet list from subnet_id to values.
for n in networks: 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] return [Network(n) for n in networks]
@ -256,3 +265,53 @@ def port_modify(request, port_id, **kwargs):
body = {'port': kwargs} body = {'port': kwargs}
port = quantumclient(request).update_port(port_id, body=body).get('port') port = quantumclient(request).update_port(port_id, body=body).get('port')
return Port(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)

View File

@ -23,7 +23,7 @@ class SystemPanels(horizon.PanelGroup):
slug = "admin" slug = "admin"
name = _("System Panel") name = _("System Panel")
panels = ('overview', 'instances', 'volumes', 'flavors', panels = ('overview', 'instances', 'volumes', 'flavors',
'images', 'projects', 'users', 'networks', 'info') 'images', 'projects', 'users', 'networks', 'routers', 'info')
class Admin(horizon.Dashboard): class Admin(horizon.Dashboard):

View File

@ -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

View File

@ -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)

View File

@ -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'

View File

@ -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, )

View File

@ -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,)

View File

@ -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'))

View File

@ -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'

View File

@ -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, )

View File

@ -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,)

View File

@ -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 %}

View File

@ -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>

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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)

View File

@ -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'),
)

View File

@ -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")

View File

@ -27,7 +27,8 @@ class BasePanels(horizon.PanelGroup):
'volumes', 'volumes',
'images_and_snapshots', 'images_and_snapshots',
'access_and_security', 'access_and_security',
'networks') 'networks',
'routers')
class ObjectStorePanels(horizon.PanelGroup): class ObjectStorePanels(horizon.PanelGroup):

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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, )

View File

@ -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,)

View File

@ -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'))

View File

@ -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'

View File

@ -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, )

View File

@ -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,)

View File

@ -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 %}

View File

@ -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>

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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)

View File

@ -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'),
)

View File

@ -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")

View File

@ -203,3 +203,70 @@ class QuantumApiTests(test.APITestCase):
self.mox.ReplayAll() self.mox.ReplayAll()
api.quantum.port_delete(self.request, port_id) 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)

View File

@ -14,7 +14,7 @@
import copy 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 from .utils import TestDataContainer
@ -24,11 +24,13 @@ def data(TEST):
TEST.networks = TestDataContainer() TEST.networks = TestDataContainer()
TEST.subnets = TestDataContainer() TEST.subnets = TestDataContainer()
TEST.ports = TestDataContainer() TEST.ports = TestDataContainer()
TEST.routers = TestDataContainer()
# data return by quantumclient # data return by quantumclient
TEST.api_networks = TestDataContainer() TEST.api_networks = TestDataContainer()
TEST.api_subnets = TestDataContainer() TEST.api_subnets = TestDataContainer()
TEST.api_ports = TestDataContainer() TEST.api_ports = TestDataContainer()
TEST.api_routers = TestDataContainer()
# 1st network # 1st network
network_dict = {'admin_state_up': True, network_dict = {'admin_state_up': True,
@ -62,7 +64,6 @@ def data(TEST):
'network_id': network_dict['id'], 'network_id': network_dict['id'],
'status': 'ACTIVE', 'status': 'ACTIVE',
'tenant_id': network_dict['tenant_id']} 'tenant_id': network_dict['tenant_id']}
TEST.api_networks.add(network_dict) TEST.api_networks.add(network_dict)
TEST.api_subnets.add(subnet_dict) TEST.api_subnets.add(subnet_dict)
TEST.api_ports.add(port_dict) TEST.api_ports.add(port_dict)
@ -109,7 +110,6 @@ def data(TEST):
'network_id': network_dict['id'], 'network_id': network_dict['id'],
'status': 'ACTIVE', 'status': 'ACTIVE',
'tenant_id': network_dict['tenant_id']} 'tenant_id': network_dict['tenant_id']}
TEST.api_networks.add(network_dict) TEST.api_networks.add(network_dict)
TEST.api_subnets.add(subnet_dict) TEST.api_subnets.add(subnet_dict)
TEST.api_ports.add(port_dict) TEST.api_ports.add(port_dict)
@ -120,3 +120,29 @@ def data(TEST):
TEST.networks.add(Network(network)) TEST.networks.add(Network(network))
TEST.subnets.add(subnet) TEST.subnets.add(subnet)
TEST.ports.add(Port(port_dict)) 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))