Improve Quantum panels to Folsom advanced features

Implements blueprint improve-quantum-summary-table

* Improve displayed columns in network related tables
* Use workflows in subnet create/update panels

* Operations
  * admin_state control for network and port
  * router:external support in network creation and update
  * No gateway support in subnet creation and update
  * enable_dhcp, allocation_pools, dns_nameservers and host_routes support
    in subnet creattion and update
  * Setting device_owner is supported in admin port panel

* Detail panels
  * router:external and provider network information in admin network detail
  * enable_dhcp, host_routes and dns_nameservers in subnet detail
  * device_owner in port detail

* Behavior changes
  * Remove created network when subnet creation failed in "Create Network".
    Before this commit a created network remains even when an associated
    subnet failed to be created, but it is a little confusing since
    an unintended network without subnet is created. This commit deletes
    such networks and display a message indicating it.

Change-Id: I1325c415acc6afc664879540c66957874d1c95c3
This commit is contained in:
Akihiro MOTOKI 2013-01-04 18:33:03 +09:00
parent 4d2199d8fe
commit 31d55e503d
33 changed files with 1559 additions and 697 deletions

View File

@ -50,19 +50,19 @@ class QuantumAPIDictWrapper(APIDictWrapper):
class Network(QuantumAPIDictWrapper): class Network(QuantumAPIDictWrapper):
"""Wrapper for quantum Networks""" """Wrapper for quantum Networks"""
_attrs = ['name', 'id', 'subnets', 'tenant_id', 'status',
'admin_state_up', 'shared']
def __init__(self, apiresource): def __init__(self, apiresource):
apiresource['admin_state'] = \ apiresource['admin_state'] = \
'UP' if apiresource['admin_state_up'] else 'DOWN' 'UP' if apiresource['admin_state_up'] else 'DOWN'
# Django cannot handle a key name with a colon, so remap another key
for key in apiresource.keys():
if key.find(':'):
apiresource['__'.join(key.split(':'))] = apiresource[key]
super(Network, self).__init__(apiresource) super(Network, self).__init__(apiresource)
class Subnet(QuantumAPIDictWrapper): class Subnet(QuantumAPIDictWrapper):
"""Wrapper for quantum subnets""" """Wrapper for quantum subnets"""
_attrs = ['name', 'id', 'cidr', 'network_id', 'tenant_id',
'ip_version', 'ipver_str']
def __init__(self, apiresource): def __init__(self, apiresource):
apiresource['ipver_str'] = get_ipver_str(apiresource['ip_version']) apiresource['ipver_str'] = get_ipver_str(apiresource['ip_version'])
@ -71,9 +71,6 @@ class Subnet(QuantumAPIDictWrapper):
class Port(QuantumAPIDictWrapper): class Port(QuantumAPIDictWrapper):
"""Wrapper for quantum ports""" """Wrapper for quantum ports"""
_attrs = ['name', 'id', 'network_id', 'tenant_id',
'admin_state_up', 'status', 'mac_address',
'fixed_ips', 'host_routes', 'device_id']
def __init__(self, apiresource): def __init__(self, apiresource):
apiresource['admin_state'] = \ apiresource['admin_state'] = \

View File

@ -34,8 +34,12 @@ class CreateNetwork(forms.SelfHandlingForm):
label=_("Name"), label=_("Name"),
required=False) required=False)
tenant_id = forms.ChoiceField(label=_("Project")) tenant_id = forms.ChoiceField(label=_("Project"))
admin_state = forms.BooleanField(label=_("Admin State"),
initial=True, required=False)
shared = forms.BooleanField(label=_("Shared"), shared = forms.BooleanField(label=_("Shared"),
initial=False, required=False) initial=False, required=False)
external = forms.BooleanField(label=_("External Network"),
initial=False, required=False)
@classmethod @classmethod
def _instantiate(cls, request, *args, **kwargs): def _instantiate(cls, request, *args, **kwargs):
@ -51,10 +55,12 @@ class CreateNetwork(forms.SelfHandlingForm):
def handle(self, request, data): def handle(self, request, data):
try: try:
network = api.quantum.network_create(request, params = {'name': data['name'],
name=data['name'], 'tenant_id': data['tenant_id'],
tenant_id=data['tenant_id'], 'admin_state_up': data['admin_state'],
shared=data['shared']) 'shared': data['shared'],
'router:external': data['external']}
network = api.quantum.network_create(request, **params)
msg = _('Network %s was successfully created.') % data['name'] msg = _('Network %s was successfully created.') % data['name']
LOG.debug(msg) LOG.debug(msg)
messages.success(request, msg) messages.success(request, msg)
@ -71,14 +77,19 @@ class UpdateNetwork(forms.SelfHandlingForm):
network_id = forms.CharField(label=_("ID"), network_id = forms.CharField(label=_("ID"),
widget=forms.TextInput( widget=forms.TextInput(
attrs={'readonly': 'readonly'})) attrs={'readonly': 'readonly'}))
admin_state = forms.BooleanField(label=_("Admin State"), required=False)
shared = forms.BooleanField(label=_("Shared"), required=False) shared = forms.BooleanField(label=_("Shared"), required=False)
external = forms.BooleanField(label=_("External Network"), required=False)
failure_url = 'horizon:admin:networks:index' failure_url = 'horizon:admin:networks:index'
def handle(self, request, data): def handle(self, request, data):
try: try:
params = {'name': data['name'],
'admin_state_up': data['admin_state'],
'shared': data['shared'],
'router:external': data['external']}
network = api.quantum.network_modify(request, data['network_id'], network = api.quantum.network_modify(request, data['network_id'],
name=data['name'], **params)
shared=data['shared'])
msg = _('Network %s was successfully updated.') % data['name'] msg = _('Network %s was successfully updated.') % data['name']
LOG.debug(msg) LOG.debug(msg)
messages.success(request, msg) messages.success(request, msg)

View File

@ -24,6 +24,8 @@ from horizon import forms
from horizon import messages from horizon import messages
from openstack_dashboard import api from openstack_dashboard import api
from openstack_dashboard.dashboards.project.networks.ports \
import forms as project_forms
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -39,9 +41,14 @@ class CreatePort(forms.SelfHandlingForm):
name = forms.CharField(max_length=255, name = forms.CharField(max_length=255,
label=_("Name"), label=_("Name"),
required=False) required=False)
admin_state = forms.BooleanField(label=_("Admin State"),
initial=True, required=False)
device_id = forms.CharField(max_length=100, label=_("Device ID"), device_id = forms.CharField(max_length=100, label=_("Device ID"),
help_text='Device ID attached to the port', help_text='Device ID attached to the port',
required=False) required=False)
device_owner = forms.CharField(max_length=100, label=_("Device Owner"),
help_text='Device owner attached to the port',
required=False)
def handle(self, request, data): def handle(self, request, data):
try: try:
@ -49,6 +56,8 @@ class CreatePort(forms.SelfHandlingForm):
# created for if admin user does not belong to the tenant. # created for if admin user does not belong to the tenant.
network = api.quantum.network_get(request, data['network_id']) network = api.quantum.network_get(request, data['network_id'])
data['tenant_id'] = network.tenant_id data['tenant_id'] = network.tenant_id
data['admin_state_up'] = data['admin_state']
del data['admin_state']
port = api.quantum.port_create(request, **data) port = api.quantum.port_create(request, **data)
msg = _('Port %s was successfully created.') % port['id'] msg = _('Port %s was successfully created.') % port['id']
@ -64,23 +73,24 @@ class CreatePort(forms.SelfHandlingForm):
exceptions.handle(request, msg, redirect=redirect) exceptions.handle(request, msg, redirect=redirect)
class UpdatePort(forms.SelfHandlingForm): class UpdatePort(project_forms.UpdatePort):
network_id = forms.CharField(widget=forms.HiddenInput()) #tenant_id = forms.CharField(widget=forms.HiddenInput())
tenant_id = forms.CharField(widget=forms.HiddenInput())
port_id = forms.CharField(widget=forms.HiddenInput())
name = forms.CharField(max_length=255,
label=_("Name"),
required=False)
device_id = forms.CharField(max_length=100, label=_("Device ID"), device_id = forms.CharField(max_length=100, label=_("Device ID"),
help_text='Device ID attached to the port', help_text='Device ID attached to the port',
required=False) required=False)
device_owner = forms.CharField(max_length=100, label=_("Device Owner"),
help_text='Device owner attached to the port',
required=False)
failure_url = 'horizon:admin:networks:detail'
def handle(self, request, data): def handle(self, request, data):
try: try:
LOG.debug('params = %s' % data) LOG.debug('params = %s' % data)
port = api.quantum.port_modify(request, data['port_id'], port = api.quantum.port_modify(request, data['port_id'],
name=data['name'], name=data['name'],
device_id=data['device_id']) admin_state_up=data['admin_state'],
device_id=data['device_id'],
device_owner=data['device_owner'])
msg = _('Port %s was successfully updated.') % data['port_id'] msg = _('Port %s was successfully updated.') % data['port_id']
LOG.debug(msg) LOG.debug(msg)
messages.success(request, msg) messages.success(request, msg)
@ -88,6 +98,6 @@ class UpdatePort(forms.SelfHandlingForm):
except Exception: except Exception:
msg = _('Failed to update port %s') % data['port_id'] msg = _('Failed to update port %s') % data['port_id']
LOG.info(msg) LOG.info(msg)
redirect = reverse('horizon:admin:networks:detail', redirect = reverse(self.failure_url,
args=[data['network_id']]) args=[data['network_id']])
exceptions.handle(request, msg, redirect=redirect) exceptions.handle(request, msg, redirect=redirect)

View File

@ -23,6 +23,9 @@ from horizon import exceptions
from horizon import forms from horizon import forms
from openstack_dashboard import api from openstack_dashboard import api
from openstack_dashboard.dashboards.project.networks.ports \
import views as project_views
from .forms import CreatePort, UpdatePort from .forms import CreatePort, UpdatePort
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -32,6 +35,7 @@ class CreateView(forms.ModalFormView):
form_class = CreatePort form_class = CreatePort
template_name = 'admin/networks/ports/create.html' template_name = 'admin/networks/ports/create.html'
success_url = 'horizon:admin:networks:detail' success_url = 'horizon:admin:networks:detail'
failure_url = 'horizon:admin:networks:detail'
def get_success_url(self): def get_success_url(self):
return reverse(self.success_url, return reverse(self.success_url,
@ -44,7 +48,7 @@ class CreateView(forms.ModalFormView):
self._object = api.quantum.network_get(self.request, self._object = api.quantum.network_get(self.request,
network_id) network_id)
except: except:
redirect = reverse("horizon:admin:networks:detail", redirect = reverse(self.failure_url,
args=(self.kwargs['network_id'],)) args=(self.kwargs['network_id'],))
msg = _("Unable to retrieve network.") msg = _("Unable to retrieve network.")
exceptions.handle(self.request, msg, redirect=redirect) exceptions.handle(self.request, msg, redirect=redirect)
@ -61,39 +65,8 @@ class CreateView(forms.ModalFormView):
"network_name": network.name} "network_name": network.name}
class UpdateView(forms.ModalFormView): class UpdateView(project_views.UpdateView):
form_class = UpdatePort form_class = UpdatePort
template_name = 'admin/networks/ports/update.html' template_name = 'admin/networks/ports/update.html'
context_object_name = 'port' context_object_name = 'port'
success_url = 'horizon:admin:networks:detail' success_url = 'horizon:admin:networks:detail'
def get_success_url(self):
return reverse(self.success_url,
args=(self.kwargs['network_id'],))
def _get_object(self, *args, **kwargs):
if not hasattr(self, "_object"):
port_id = self.kwargs['port_id']
try:
self._object = api.quantum.port_get(self.request, port_id)
except:
redirect = reverse("horizon:admin:networks:detail",
args=(self.kwargs['network_id'],))
msg = _('Unable to retrieve port details')
exceptions.handle(self.request, msg, redirect=redirect)
return self._object
def get_context_data(self, **kwargs):
context = super(UpdateView, self).get_context_data(**kwargs)
port = self._get_object()
context['port_id'] = port['id']
context['network_id'] = port['network_id']
return context
def get_initial(self):
port = self._get_object()
return {'port_id': port['id'],
'network_id': port['network_id'],
'tenant_id': port['tenant_id'],
'name': port['name'],
'device_id': port['device_id']}

View File

@ -1,53 +0,0 @@
# 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.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import forms
from horizon import exceptions
from openstack_dashboard import api
from openstack_dashboard.dashboards.project.networks.subnets import \
forms as user_forms
LOG = logging.getLogger(__name__)
class CreateSubnet(user_forms.CreateSubnet):
failure_url = 'horizon:admin:networks:detail'
def handle(self, request, data):
try:
# We must specify tenant_id of the network which a subnet is
# created for if admin user does not belong to the tenant.
network = api.quantum.network_get(request, data['network_id'])
data['tenant_id'] = network.tenant_id
except:
msg = _('Failed to retrieve network %s for a subnet') \
% data['network_id']
LOG.info(msg)
redirect = reverse(self.failure_url, args=[data['network_id']])
exceptions.handle(request, msg, redirect=redirect)
return super(CreateSubnet, self).handle(request, data)
class UpdateSubnet(user_forms.UpdateSubnet):
tenant_id = forms.CharField(widget=forms.HiddenInput())
failure_url = 'horizon:admin:networks:detail'

View File

@ -23,81 +23,20 @@ from horizon import exceptions
from horizon import forms from horizon import forms
from openstack_dashboard import api from openstack_dashboard import api
from .forms import CreateSubnet, UpdateSubnet from openstack_dashboard.dashboards.project.networks.subnets \
import views as project_views
from .workflows import CreateSubnet, UpdateSubnet
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class CreateView(forms.ModalFormView): class CreateView(project_views.CreateView):
form_class = CreateSubnet workflow_class = CreateSubnet
template_name = 'admin/networks/subnets/create.html' template_name = 'admin/networks/subnets/create.html'
success_url = 'horizon:admin:networks:detail'
def get_success_url(self):
return reverse(self.success_url,
args=(self.kwargs['network_id'],))
def get_object(self):
if not hasattr(self, "_object"):
try:
network_id = self.kwargs["network_id"]
self._object = api.quantum.network_get(self.request,
network_id)
except:
redirect = reverse('horizon:project:networks:index')
msg = _("Unable to retrieve network.")
exceptions.handle(self.request, msg, redirect=redirect)
return self._object
def get_context_data(self, **kwargs):
context = super(CreateView, self).get_context_data(**kwargs)
context['network'] = self.get_object()
return context
def get_initial(self):
network = self.get_object()
return {"network_id": self.kwargs['network_id'],
"network_name": network.name}
class UpdateView(forms.ModalFormView): class UpdateView(project_views.UpdateView):
form_class = UpdateSubnet workflow_class = UpdateSubnet
template_name = 'admin/networks/subnets/update.html' template_name = 'admin/networks/subnets/update.html'
context_object_name = 'subnet'
success_url = 'horizon:admin:networks:detail'
def get_success_url(self):
return reverse(self.success_url,
args=(self.kwargs['network_id'],))
def _get_object(self, *args, **kwargs):
if not hasattr(self, "_object"):
subnet_id = self.kwargs['subnet_id']
try:
self._object = api.quantum.subnet_get(self.request, subnet_id)
except:
redirect = reverse("horizon:admin:networks:detail",
args=(self.kwargs['network_id'],))
msg = _('Unable to retrieve subnet details')
exceptions.handle(self.request, msg, redirect=redirect)
return self._object
def get_context_data(self, **kwargs):
context = super(UpdateView, self).get_context_data(**kwargs)
subnet = self._get_object()
context['subnet_id'] = subnet['id']
context['network_id'] = subnet['network_id']
context['cidr'] = subnet['cidr']
context['ip_version'] = {4: 'IPv4', 6: 'IPv6'}[subnet['ip_version']]
return context
def get_initial(self):
subnet = self._get_object()
return {'network_id': self.kwargs['network_id'],
'subnet_id': subnet['id'],
'tenant_id': subnet['tenant_id'],
'cidr': subnet['cidr'],
'ip_version': subnet['ip_version'],
'name': subnet['name'],
'gateway_ip': subnet['gateway_ip']}

View File

@ -0,0 +1,60 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 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.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from horizon import exceptions
from openstack_dashboard import api
from openstack_dashboard.dashboards.project.networks.subnets \
import workflows as project_workflows
LOG = logging.getLogger(__name__)
class CreateSubnet(project_workflows.CreateSubnet):
def get_success_url(self):
return reverse("horizon:admin:networks:detail",
args=(self.context.get('network_id'),))
def get_failure_url(self):
return reverse("horizon:admin:networks:detail",
args=(self.context.get('network_id'),))
def handle(self, request, data):
try:
# We must specify tenant_id of the network which a subnet is
# created for if admin user does not belong to the tenant.
network = api.quantum.network_get(request,
self.context['network_id'])
except:
msg = (_('Failed to retrieve network %s for a subnet') %
data['network_id'])
LOG.info(msg)
redirect = self.get_failure_url()
exceptions.handle(request, msg, redirect=redirect)
subnet = self._create_subnet(request, data,
tenant_id=network.tenant_id)
return True if subnet else False
class UpdateSubnet(project_workflows.UpdateSubnet):
success_url = "horizon:admin:networks:detail"
failure_url = "horizon:admin:networks:detail"

View File

@ -1,25 +0,0 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block form_id %}create_subnet_form{% endblock %}
{% block form_action %}{% url horizon:admin:networks:addsubnet network.id %}
{% endblock %}
{% block modal-header %}{% trans "Create Subnet" %}{% 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 create a subnet for the network. Any network address can be specified unless the network address does not overlap other subnets in the network." %}</p>
</div>
{% endblock %}
{% block modal-footer %}
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Subnet" %}" />
<a href="{% url horizon:admin:networks:detail network.id %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
{% endblock %}

View File

@ -1,33 +0,0 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block form_id %}update_subnet_form{% endblock %}
{% block form_action %}{% url horizon:admin:networks:editsubnet network_id subnet_id %}{% endblock %}
{% block modal-header %}{% trans "Edit Subnet" %}{% endblock %}
{% block modal-body %}
<div class="left">
<dl>
<dt>{% trans "ID" %}</dt>
<dd>{{ subnet_id }}</dd>
<dt>{% trans "Network Address" %}</dt>
<dd>{{ cidr }}</dd>
<dt>{% trans "IP version" %}</dt>
<dd>{{ ip_version }}</dd>
</dl>
<hr>
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
</div>
<div class="right">
<h3>{% trans "Description:" %}</h3>
<p>{% trans "You may update the editable properties of your subnet here." %}</p>
</div>
{% endblock %}
{% block modal-footer %}
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Save Changes" %}" />
<a href="{% url horizon:admin:networks:detail network_id %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
{% endblock %}

View File

@ -7,5 +7,5 @@
{% endblock page_header %} {% endblock page_header %}
{% block main %} {% block main %}
{% include "admin/networks/subnets/_create.html" %} {% include "horizon/common/_workflow.html" %}
{% endblock %} {% endblock %}

View File

@ -7,5 +7,5 @@
{% endblock page_header %} {% endblock page_header %}
{% block main %} {% block main %}
{% include 'admin/networks/subnets/_update.html' %} {% include "horizon/common/_workflow.html" %}
{% endblock %} {% endblock %}

View File

@ -21,6 +21,8 @@ from mox import IsA
from openstack_dashboard import api from openstack_dashboard import api
from openstack_dashboard.test import helpers as test from openstack_dashboard.test import helpers as test
from openstack_dashboard.dashboards.project.networks.tests \
import form_data_subnet
INDEX_URL = reverse('horizon:admin:networks:index') INDEX_URL = reverse('horizon:admin:networks:index')
@ -166,13 +168,19 @@ class NetworkTests(test.BaseAdminViewTests):
network = self.networks.first() network = self.networks.first()
api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\ api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\
.AndReturn(tenants) .AndReturn(tenants)
api.quantum.network_create(IsA(http.HttpRequest), name=network.name, params = {'name': network.name,
tenant_id=tenant_id, shared=True)\ 'tenant_id': tenant_id,
'admin_state_up': network.admin_state_up,
'router:external': True,
'shared': True}
api.quantum.network_create(IsA(http.HttpRequest), **params)\
.AndReturn(network) .AndReturn(network)
self.mox.ReplayAll() self.mox.ReplayAll()
form_data = {'tenant_id': tenant_id, form_data = {'tenant_id': tenant_id,
'name': network.name, 'name': network.name,
'admin_state': network.admin_state_up,
'external': True,
'shared': True} 'shared': True}
url = reverse('horizon:admin:networks:create') url = reverse('horizon:admin:networks:create')
res = self.client.post(url, form_data) res = self.client.post(url, form_data)
@ -188,13 +196,19 @@ class NetworkTests(test.BaseAdminViewTests):
network = self.networks.first() network = self.networks.first()
api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\ api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\
.AndReturn(tenants) .AndReturn(tenants)
api.quantum.network_create(IsA(http.HttpRequest), name=network.name, params = {'name': network.name,
tenant_id=tenant_id, shared=False)\ 'tenant_id': tenant_id,
'admin_state_up': network.admin_state_up,
'router:external': True,
'shared': False}
api.quantum.network_create(IsA(http.HttpRequest), **params)\
.AndRaise(self.exceptions.quantum) .AndRaise(self.exceptions.quantum)
self.mox.ReplayAll() self.mox.ReplayAll()
form_data = {'tenant_id': tenant_id, form_data = {'tenant_id': tenant_id,
'name': network.name, 'name': network.name,
'admin_state': network.admin_state_up,
'external': True,
'shared': False} 'shared': False}
url = reverse('horizon:admin:networks:create') url = reverse('horizon:admin:networks:create')
res = self.client.post(url, form_data) res = self.client.post(url, form_data)
@ -233,19 +247,25 @@ class NetworkTests(test.BaseAdminViewTests):
'network_get',)}) 'network_get',)})
def test_network_update_post(self): def test_network_update_post(self):
network = self.networks.first() network = self.networks.first()
params = {'name': network.name,
'shared': True,
'admin_state_up': network.admin_state_up,
'router:external': True}
api.quantum.network_modify(IsA(http.HttpRequest), network.id, api.quantum.network_modify(IsA(http.HttpRequest), network.id,
name=network.name, shared=True)\ **params)\
.AndReturn(network) .AndReturn(network)
api.quantum.network_get(IsA(http.HttpRequest), network.id)\ api.quantum.network_get(IsA(http.HttpRequest), network.id)\
.AndReturn(network) .AndReturn(network)
self.mox.ReplayAll() self.mox.ReplayAll()
formData = {'network_id': network.id, form_data = {'network_id': network.id,
'name': network.name, 'name': network.name,
'tenant_id': network.tenant_id, 'tenant_id': network.tenant_id,
'shared': True} 'admin_state': network.admin_state_up,
'shared': True,
'external': True}
url = reverse('horizon:admin:networks:update', args=[network.id]) url = reverse('horizon:admin:networks:update', args=[network.id])
res = self.client.post(url, formData) res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, INDEX_URL) self.assertRedirectsNoFollow(res, INDEX_URL)
@ -253,8 +273,12 @@ class NetworkTests(test.BaseAdminViewTests):
'network_get',)}) 'network_get',)})
def test_network_update_post_exception(self): def test_network_update_post_exception(self):
network = self.networks.first() network = self.networks.first()
params = {'name': network.name,
'shared': False,
'admin_state_up': network.admin_state_up,
'router:external': False}
api.quantum.network_modify(IsA(http.HttpRequest), network.id, api.quantum.network_modify(IsA(http.HttpRequest), network.id,
name=network.name, shared=False)\ **params)\
.AndRaise(self.exceptions.quantum) .AndRaise(self.exceptions.quantum)
api.quantum.network_get(IsA(http.HttpRequest), network.id)\ api.quantum.network_get(IsA(http.HttpRequest), network.id)\
.AndReturn(network) .AndReturn(network)
@ -263,7 +287,9 @@ class NetworkTests(test.BaseAdminViewTests):
form_data = {'network_id': network.id, form_data = {'network_id': network.id,
'name': network.name, 'name': network.name,
'tenant_id': network.tenant_id, 'tenant_id': network.tenant_id,
'shared': False} 'admin_state': network.admin_state_up,
'shared': False,
'external': False}
url = reverse('horizon:admin:networks:update', args=[network.id]) url = reverse('horizon:admin:networks:update', args=[network.id])
res = self.client.post(url, form_data) res = self.client.post(url, form_data)
@ -308,6 +334,9 @@ class NetworkTests(test.BaseAdminViewTests):
self.assertRedirectsNoFollow(res, INDEX_URL) self.assertRedirectsNoFollow(res, INDEX_URL)
class NetworkSubnetTests(test.BaseAdminViewTests):
@test.create_stubs({api.quantum: ('subnet_get',)}) @test.create_stubs({api.quantum: ('subnet_get',)})
def test_subnet_detail(self): def test_subnet_detail(self):
subnet = self.subnets.first() subnet = self.subnets.first()
@ -367,21 +396,17 @@ class NetworkTests(test.BaseAdminViewTests):
.AndReturn(self.networks.first()) .AndReturn(self.networks.first())
api.quantum.subnet_create(IsA(http.HttpRequest), api.quantum.subnet_create(IsA(http.HttpRequest),
network_id=network.id, network_id=network.id,
network_name=network.name,
name=subnet.name, name=subnet.name,
cidr=subnet.cidr, cidr=subnet.cidr,
ip_version=subnet.ip_version, ip_version=subnet.ip_version,
gateway_ip=subnet.gateway_ip, gateway_ip=subnet.gateway_ip,
enable_dhcp=subnet.enable_dhcp,
allocation_pools=subnet.allocation_pools,
tenant_id=subnet.tenant_id)\ tenant_id=subnet.tenant_id)\
.AndReturn(subnet) .AndReturn(subnet)
self.mox.ReplayAll() self.mox.ReplayAll()
form_data = {'network_id': subnet.network_id, form_data = form_data_subnet(subnet)
'network_name': network.name,
'name': subnet.name,
'cidr': subnet.cidr,
'ip_version': subnet.ip_version,
'gateway_ip': subnet.gateway_ip}
url = reverse('horizon:admin:networks:addsubnet', url = reverse('horizon:admin:networks:addsubnet',
args=[subnet.network_id]) args=[subnet.network_id])
res = self.client.post(url, form_data) res = self.client.post(url, form_data)
@ -401,12 +426,7 @@ class NetworkTests(test.BaseAdminViewTests):
.AndRaise(self.exceptions.quantum) .AndRaise(self.exceptions.quantum)
self.mox.ReplayAll() self.mox.ReplayAll()
form_data = {'network_id': subnet.network_id, form_data = form_data_subnet(subnet, allocation_pools=[])
'network_name': network.name,
'name': subnet.name,
'cidr': subnet.cidr,
'ip_version': subnet.ip_version,
'gateway_ip': subnet.gateway_ip}
url = reverse('horizon:admin:networks:addsubnet', url = reverse('horizon:admin:networks:addsubnet',
args=[subnet.network_id]) args=[subnet.network_id])
res = self.client.post(url, form_data) res = self.client.post(url, form_data)
@ -430,21 +450,16 @@ class NetworkTests(test.BaseAdminViewTests):
.AndReturn(self.networks.first()) .AndReturn(self.networks.first())
api.quantum.subnet_create(IsA(http.HttpRequest), api.quantum.subnet_create(IsA(http.HttpRequest),
network_id=network.id, network_id=network.id,
network_name=network.name,
name=subnet.name, name=subnet.name,
cidr=subnet.cidr, cidr=subnet.cidr,
ip_version=subnet.ip_version, ip_version=subnet.ip_version,
gateway_ip=subnet.gateway_ip, gateway_ip=subnet.gateway_ip,
enable_dhcp=subnet.enable_dhcp,
tenant_id=subnet.tenant_id)\ tenant_id=subnet.tenant_id)\
.AndRaise(self.exceptions.quantum) .AndRaise(self.exceptions.quantum)
self.mox.ReplayAll() self.mox.ReplayAll()
form_data = {'network_id': subnet.network_id, form_data = form_data_subnet(subnet, allocation_pools=[])
'network_name': network.name,
'name': subnet.name,
'cidr': subnet.cidr,
'ip_version': subnet.ip_version,
'gateway_ip': subnet.gateway_ip}
url = reverse('horizon:admin:networks:addsubnet', url = reverse('horizon:admin:networks:addsubnet',
args=[subnet.network_id]) args=[subnet.network_id])
res = self.client.post(url, form_data) res = self.client.post(url, form_data)
@ -464,12 +479,7 @@ class NetworkTests(test.BaseAdminViewTests):
# dummy IPv6 address # dummy IPv6 address
cidr = '2001:0DB8:0:CD30:123:4567:89AB:CDEF/60' cidr = '2001:0DB8:0:CD30:123:4567:89AB:CDEF/60'
form_data = {'network_id': subnet.network_id, form_data = form_data_subnet(subnet, cidr=cidr, allocation_pools=[])
'network_name': network.name,
'name': subnet.name,
'cidr': cidr,
'ip_version': subnet.ip_version,
'gateway_ip': subnet.gateway_ip}
url = reverse('horizon:admin:networks:addsubnet', url = reverse('horizon:admin:networks:addsubnet',
args=[subnet.network_id]) args=[subnet.network_id])
res = self.client.post(url, form_data) res = self.client.post(url, form_data)
@ -488,12 +498,8 @@ class NetworkTests(test.BaseAdminViewTests):
# dummy IPv6 address # dummy IPv6 address
gateway_ip = '2001:0DB8:0:CD30:123:4567:89AB:CDEF' gateway_ip = '2001:0DB8:0:CD30:123:4567:89AB:CDEF'
form_data = {'network_id': subnet.network_id, form_data = form_data_subnet(subnet, gateway_ip=gateway_ip,
'network_name': network.name, allocation_pools=[])
'name': subnet.name,
'cidr': subnet.cidr,
'ip_version': subnet.ip_version,
'gateway_ip': gateway_ip}
url = reverse('horizon:admin:networks:addsubnet', url = reverse('horizon:admin:networks:addsubnet',
args=[subnet.network_id]) args=[subnet.network_id])
res = self.client.post(url, form_data) res = self.client.post(url, form_data)
@ -508,20 +514,17 @@ class NetworkTests(test.BaseAdminViewTests):
.AndReturn(subnet) .AndReturn(subnet)
api.quantum.subnet_modify(IsA(http.HttpRequest), subnet.id, api.quantum.subnet_modify(IsA(http.HttpRequest), subnet.id,
name=subnet.name, name=subnet.name,
gateway_ip=subnet.gateway_ip)\ gateway_ip=subnet.gateway_ip,
enable_dhcp=subnet.enable_dhcp,
dns_nameservers=[],
host_routes=[])\
.AndReturn(subnet) .AndReturn(subnet)
self.mox.ReplayAll() self.mox.ReplayAll()
formData = {'network_id': subnet.network_id, form_data = form_data_subnet(subnet, allocation_pools=[])
'tenant_id': subnet.tenant_id,
'subnet_id': subnet.id,
'name': subnet.name,
'cidr': subnet.cidr,
'ip_version': subnet.ip_version,
'gateway_ip': subnet.gateway_ip}
url = reverse('horizon:admin:networks:editsubnet', url = reverse('horizon:admin:networks:editsubnet',
args=[subnet.network_id, subnet.id]) args=[subnet.network_id, subnet.id])
res = self.client.post(url, formData) res = self.client.post(url, form_data)
redir_url = reverse('horizon:admin:networks:detail', redir_url = reverse('horizon:admin:networks:detail',
args=[subnet.network_id]) args=[subnet.network_id])
@ -537,16 +540,11 @@ class NetworkTests(test.BaseAdminViewTests):
# dummy IPv6 address # dummy IPv6 address
gateway_ip = '2001:0DB8:0:CD30:123:4567:89AB:CDEF' gateway_ip = '2001:0DB8:0:CD30:123:4567:89AB:CDEF'
formData = {'network_id': subnet.network_id, form_data = form_data_subnet(subnet, gateway_ip=gateway_ip,
'tenant_id': subnet.tenant_id, allocation_pools=[])
'subnet_id': subnet.id,
'name': subnet.name,
'cidr': subnet.cidr,
'ip_version': subnet.ip_version,
'gateway_ip': gateway_ip}
url = reverse('horizon:admin:networks:editsubnet', url = reverse('horizon:admin:networks:editsubnet',
args=[subnet.network_id, subnet.id]) args=[subnet.network_id, subnet.id])
res = self.client.post(url, formData) res = self.client.post(url, form_data)
self.assertContains(res, 'Gateway IP and IP version are inconsistent.') self.assertContains(res, 'Gateway IP and IP version are inconsistent.')
@ -563,10 +561,10 @@ class NetworkTests(test.BaseAdminViewTests):
.AndReturn([self.ports.first()]) .AndReturn([self.ports.first()])
self.mox.ReplayAll() self.mox.ReplayAll()
formData = {'action': 'subnets__delete__%s' % subnet.id} form_data = {'action': 'subnets__delete__%s' % subnet.id}
url = reverse('horizon:admin:networks:detail', url = reverse('horizon:admin:networks:detail',
args=[network_id]) args=[network_id])
res = self.client.post(url, formData) res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, url) self.assertRedirectsNoFollow(res, url)
@ -584,13 +582,16 @@ class NetworkTests(test.BaseAdminViewTests):
.AndReturn([self.ports.first()]) .AndReturn([self.ports.first()])
self.mox.ReplayAll() self.mox.ReplayAll()
formData = {'action': 'subnets__delete__%s' % subnet.id} form_data = {'action': 'subnets__delete__%s' % subnet.id}
url = reverse('horizon:admin:networks:detail', url = reverse('horizon:admin:networks:detail',
args=[network_id]) args=[network_id])
res = self.client.post(url, formData) res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, url) self.assertRedirectsNoFollow(res, url)
class NetworkPortTests(test.BaseAdminViewTests):
@test.create_stubs({api.quantum: ('port_get',)}) @test.create_stubs({api.quantum: ('port_get',)})
def test_port_detail(self): def test_port_detail(self):
port = self.ports.first() port = self.ports.first()
@ -651,14 +652,18 @@ class NetworkTests(test.BaseAdminViewTests):
network_id=network.id, network_id=network.id,
network_name=network.name, network_name=network.name,
name=port.name, name=port.name,
device_id=port.device_id)\ admin_state_up=port.admin_state_up,
device_id=port.device_id,
device_owner=port.device_owner)\
.AndReturn(port) .AndReturn(port)
self.mox.ReplayAll() self.mox.ReplayAll()
form_data = {'network_id': port.network_id, form_data = {'network_id': port.network_id,
'network_name': network.name, 'network_name': network.name,
'name': port.name, 'name': port.name,
'device_id': port.device_id} 'admin_state': port.admin_state_up,
'device_id': port.device_id,
'device_owner': port.device_owner}
url = reverse('horizon:admin:networks:addport', url = reverse('horizon:admin:networks:addport',
args=[port.network_id]) args=[port.network_id])
res = self.client.post(url, form_data) res = self.client.post(url, form_data)
@ -684,14 +689,18 @@ class NetworkTests(test.BaseAdminViewTests):
network_id=network.id, network_id=network.id,
network_name=network.name, network_name=network.name,
name=port.name, name=port.name,
device_id=port.device_id)\ admin_state_up=port.admin_state_up,
device_id=port.device_id,
device_owner=port.device_owner)\
.AndRaise(self.exceptions.quantum) .AndRaise(self.exceptions.quantum)
self.mox.ReplayAll() self.mox.ReplayAll()
form_data = {'network_id': port.network_id, form_data = {'network_id': port.network_id,
'network_name': network.name, 'network_name': network.name,
'name': port.name, 'name': port.name,
'device_id': port.device_id} 'admin_state': port.admin_state_up,
'device_id': port.device_id,
'device_owner': port.device_owner}
url = reverse('horizon:admin:networks:addport', url = reverse('horizon:admin:networks:addport',
args=[port.network_id]) args=[port.network_id])
res = self.client.post(url, form_data) res = self.client.post(url, form_data)
@ -722,18 +731,22 @@ class NetworkTests(test.BaseAdminViewTests):
api.quantum.port_get(IsA(http.HttpRequest), port.id)\ api.quantum.port_get(IsA(http.HttpRequest), port.id)\
.AndReturn(port) .AndReturn(port)
api.quantum.port_modify(IsA(http.HttpRequest), port.id, api.quantum.port_modify(IsA(http.HttpRequest), port.id,
name=port.name, device_id=port.device_id)\ name=port.name,
admin_state_up=port.admin_state_up,
device_id=port.device_id,
device_owner=port.device_owner)\
.AndReturn(port) .AndReturn(port)
self.mox.ReplayAll() self.mox.ReplayAll()
formData = {'tenant_id': port.tenant_id, form_data = {'network_id': port.network_id,
'network_id': port.network_id, 'port_id': port.id,
'port_id': port.id, 'name': port.name,
'name': port.name, 'admin_state': port.admin_state_up,
'device_id': port.device_id} 'device_id': port.device_id,
'device_owner': port.device_owner}
url = reverse('horizon:admin:networks:editport', url = reverse('horizon:admin:networks:editport',
args=[port.network_id, port.id]) args=[port.network_id, port.id])
res = self.client.post(url, formData) res = self.client.post(url, form_data)
redir_url = reverse('horizon:admin:networks:detail', redir_url = reverse('horizon:admin:networks:detail',
args=[port.network_id]) args=[port.network_id])
@ -746,18 +759,22 @@ class NetworkTests(test.BaseAdminViewTests):
api.quantum.port_get(IsA(http.HttpRequest), port.id)\ api.quantum.port_get(IsA(http.HttpRequest), port.id)\
.AndReturn(port) .AndReturn(port)
api.quantum.port_modify(IsA(http.HttpRequest), port.id, api.quantum.port_modify(IsA(http.HttpRequest), port.id,
name=port.name, device_id=port.device_id)\ name=port.name,
admin_state_up=port.admin_state_up,
device_id=port.device_id,
device_owner=port.device_owner)\
.AndRaise(self.exceptions.quantum) .AndRaise(self.exceptions.quantum)
self.mox.ReplayAll() self.mox.ReplayAll()
formData = {'tenant_id': port.tenant_id, form_data = {'network_id': port.network_id,
'network_id': port.network_id, 'port_id': port.id,
'port_id': port.id, 'name': port.name,
'name': port.name, 'admin_state': port.admin_state_up,
'device_id': port.device_id} 'device_id': port.device_id,
'device_owner': port.device_owner}
url = reverse('horizon:admin:networks:editport', url = reverse('horizon:admin:networks:editport',
args=[port.network_id, port.id]) args=[port.network_id, port.id])
res = self.client.post(url, formData) res = self.client.post(url, form_data)
redir_url = reverse('horizon:admin:networks:detail', redir_url = reverse('horizon:admin:networks:detail',
args=[port.network_id]) args=[port.network_id])
@ -776,10 +793,10 @@ class NetworkTests(test.BaseAdminViewTests):
.AndReturn([self.ports.first()]) .AndReturn([self.ports.first()])
self.mox.ReplayAll() self.mox.ReplayAll()
formData = {'action': 'ports__delete__%s' % port.id} form_data = {'action': 'ports__delete__%s' % port.id}
url = reverse('horizon:admin:networks:detail', url = reverse('horizon:admin:networks:detail',
args=[network_id]) args=[network_id])
res = self.client.post(url, formData) res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, url) self.assertRedirectsNoFollow(res, url)
@ -797,9 +814,9 @@ class NetworkTests(test.BaseAdminViewTests):
.AndReturn([self.ports.first()]) .AndReturn([self.ports.first()])
self.mox.ReplayAll() self.mox.ReplayAll()
formData = {'action': 'ports__delete__%s' % port.id} form_data = {'action': 'ports__delete__%s' % port.id}
url = reverse('horizon:admin:networks:detail', url = reverse('horizon:admin:networks:detail',
args=[network_id]) args=[network_id])
res = self.client.post(url, formData) res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, url) self.assertRedirectsNoFollow(res, url)

View File

@ -137,4 +137,6 @@ class UpdateView(user_views.UpdateView):
return {'network_id': network['id'], return {'network_id': network['id'],
'tenant_id': network['tenant_id'], 'tenant_id': network['tenant_id'],
'name': network['name'], 'name': network['name'],
'shared': network['shared']} 'admin_state': network['admin_state_up'],
'shared': network['shared'],
'external': network['router__external']}

View File

@ -39,12 +39,15 @@ class UpdateNetwork(forms.SelfHandlingForm):
network_id = forms.CharField(label=_("ID"), network_id = forms.CharField(label=_("ID"),
widget=forms.TextInput( widget=forms.TextInput(
attrs={'readonly': 'readonly'})) attrs={'readonly': 'readonly'}))
admin_state = forms.BooleanField(label=_("Admin State"), required=False)
failure_url = 'horizon:project:networks:index' failure_url = 'horizon:project:networks:index'
def handle(self, request, data): def handle(self, request, data):
try: try:
params = {'admin_state_up': data['admin_state'],
'name': data['name']}
network = api.quantum.network_modify(request, data['network_id'], network = api.quantum.network_modify(request, data['network_id'],
name=data['name']) **params)
msg = _('Network %s was successfully updated.') % data['name'] msg = _('Network %s was successfully updated.') % data['name']
LOG.debug(msg) LOG.debug(msg)
messages.success(request, msg) messages.success(request, msg)

View File

@ -0,0 +1,56 @@
# 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.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from openstack_dashboard import api
LOG = logging.getLogger(__name__)
class UpdatePort(forms.SelfHandlingForm):
network_id = forms.CharField(widget=forms.HiddenInput())
port_id = forms.CharField(widget=forms.HiddenInput())
name = forms.CharField(max_length=255,
label=_("Name"),
required=False)
admin_state = forms.BooleanField(label=_("Admin State"), required=False)
failure_url = 'horizon:project:networks:detail'
def handle(self, request, data):
try:
LOG.debug('params = %s' % data)
port = api.quantum.port_modify(request, data['port_id'],
name=data['name'],
admin_state_up=data['admin_state'])
msg = _('Port %s was successfully updated.') % data['port_id']
LOG.debug(msg)
messages.success(request, msg)
return port
except Exception:
msg = _('Failed to update port %s') % data['port_id']
LOG.info(msg)
redirect = reverse(self.failure_url,
args=[data['network_id']])
exceptions.handle(request, msg, redirect=redirect)

View File

@ -16,6 +16,7 @@
import logging import logging
from django.core.urlresolvers import reverse
from django import template from django import template
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -32,7 +33,23 @@ def get_fixed_ips(port):
def get_attached(port): def get_attached(port):
return _('Attached') if port['device_id'] else _('Detached') if port['device_owner']:
return port['device_owner']
elif port['device_id']:
return _('Attached')
else:
return _('Detached')
class UpdatePort(tables.LinkAction):
name = "update"
verbose_name = _("Edit Port")
url = "horizon:project:networks:editport"
classes = ("ajax-modal", "btn-edit")
def get_link_url(self, port):
network_id = self.table.kwargs['network_id']
return reverse(self.url, args=(network_id, port.id))
class PortsTable(tables.DataTable): class PortsTable(tables.DataTable):
@ -40,7 +57,7 @@ class PortsTable(tables.DataTable):
verbose_name=_("Name"), verbose_name=_("Name"),
link="horizon:project:networks:ports:detail") link="horizon:project:networks:ports:detail")
fixed_ips = tables.Column(get_fixed_ips, verbose_name=_("Fixed IPs")) fixed_ips = tables.Column(get_fixed_ips, verbose_name=_("Fixed IPs"))
attached = tables.Column(get_attached, verbose_name=_("Device Attached")) attached = tables.Column(get_attached, verbose_name=_("Attached Device"))
status = tables.Column("status", verbose_name=_("Status")) status = tables.Column("status", verbose_name=_("Status"))
admin_state = tables.Column("admin_state", admin_state = tables.Column("admin_state",
verbose_name=_("Admin State")) verbose_name=_("Admin State"))
@ -51,3 +68,4 @@ class PortsTable(tables.DataTable):
class Meta: class Meta:
name = "ports" name = "ports"
verbose_name = _("Ports") verbose_name = _("Ports")
row_actions = (UpdatePort,)

View File

@ -14,11 +14,59 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import tabs from horizon import tabs
from openstack_dashboard import api
from .forms import UpdatePort
from .tabs import PortDetailTabs from .tabs import PortDetailTabs
class DetailView(tabs.TabView): class DetailView(tabs.TabView):
tab_group_class = PortDetailTabs tab_group_class = PortDetailTabs
template_name = 'project/networks/ports/detail.html' template_name = 'project/networks/ports/detail.html'
class UpdateView(forms.ModalFormView):
form_class = UpdatePort
template_name = 'project/networks/ports/update.html'
context_object_name = 'port'
success_url = 'horizon:project:networks:detail'
def get_success_url(self):
return reverse(self.success_url,
args=(self.kwargs['network_id'],))
def _get_object(self, *args, **kwargs):
if not hasattr(self, "_object"):
port_id = self.kwargs['port_id']
try:
self._object = api.quantum.port_get(self.request, port_id)
except:
redirect = reverse("horizon:project:networks:detail",
args=(self.kwargs['network_id'],))
msg = _('Unable to retrieve port details')
exceptions.handle(self.request, msg, redirect=redirect)
return self._object
def get_context_data(self, **kwargs):
context = super(UpdateView, self).get_context_data(**kwargs)
port = self._get_object()
context['port_id'] = port['id']
context['network_id'] = port['network_id']
return context
def get_initial(self):
port = self._get_object()
return {'port_id': port['id'],
'network_id': port['network_id'],
'tenant_id': port['tenant_id'],
'name': port['name'],
'admin_state': port['admin_state_up'],
'device_id': port['device_id'],
'device_owner': port['device_owner']}

View File

@ -1,139 +0,0 @@
# 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
import netaddr
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 horizon.utils import fields
from openstack_dashboard import api
LOG = logging.getLogger(__name__)
class CreateSubnet(forms.SelfHandlingForm):
network_name = forms.CharField(label=_("Network Name"),
required=False,
widget=forms.TextInput(
attrs={'readonly': 'readonly'}))
network_id = forms.CharField(label=_("Network ID"),
widget=forms.TextInput(
attrs={'readonly': 'readonly'}))
name = forms.CharField(max_length=255,
label=_("Name"),
required=False)
cidr = fields.IPField(label=_("Network Address"),
required=True,
initial="",
help_text=_("Network address in CIDR format "
"(e.g. 192.168.0.0/24)"),
version=fields.IPv4 | fields.IPv6,
mask=True)
ip_version = forms.ChoiceField(choices=[(4, 'IPv4'), (6, 'IPv6')],
label=_("IP Version"))
gateway_ip = fields.IPField(label=_("Gateway IP"),
required=False,
initial="",
help_text=_("IP address of Gateway "
"(e.g. 192.168.0.1)"),
version=fields.IPv4 | fields.IPv6,
mask=False)
failure_url = 'horizon:project:networks:detail'
def clean(self):
cleaned_data = super(CreateSubnet, self).clean()
cidr = cleaned_data.get('cidr')
ip_version = int(cleaned_data.get('ip_version'))
gateway_ip = cleaned_data.get('gateway_ip')
if cidr:
if netaddr.IPNetwork(cidr).version is not ip_version:
msg = _('Network Address and IP version are inconsistent.')
raise forms.ValidationError(msg)
if gateway_ip:
if netaddr.IPAddress(gateway_ip).version is not ip_version:
msg = _('Gateway IP and IP version are inconsistent.')
raise forms.ValidationError(msg)
return cleaned_data
def handle(self, request, data):
try:
LOG.debug('params = %s' % data)
data['ip_version'] = int(data['ip_version'])
if not data['gateway_ip']:
del data['gateway_ip']
subnet = api.quantum.subnet_create(request, **data)
msg = _('Subnet %s was successfully created.') % data['cidr']
LOG.debug(msg)
messages.success(request, msg)
return subnet
except Exception:
msg = _('Failed to create subnet %s') % data['cidr']
LOG.info(msg)
redirect = reverse(self.failure_url, args=[data['network_id']])
exceptions.handle(request, msg, redirect=redirect)
class UpdateSubnet(forms.SelfHandlingForm):
network_id = forms.CharField(widget=forms.HiddenInput())
subnet_id = forms.CharField(widget=forms.HiddenInput())
cidr = forms.CharField(widget=forms.HiddenInput())
ip_version = forms.CharField(widget=forms.HiddenInput())
name = forms.CharField(max_length=255,
label=_("Name"),
required=False)
gateway_ip = fields.IPField(label=_("Gateway IP"),
required=True,
initial="",
help_text=_("IP address of Gateway "
"(e.g. 192.168.0.1)"),
version=fields.IPv4 | fields.IPv6,
mask=False)
failure_url = 'horizon:project:networks:detail'
def clean(self):
cleaned_data = super(UpdateSubnet, self).clean()
ip_version = int(cleaned_data.get('ip_version'))
gateway_ip = cleaned_data.get('gateway_ip')
if gateway_ip:
if netaddr.IPAddress(gateway_ip).version is not ip_version:
msg = _('Gateway IP and IP version are inconsistent.')
raise forms.ValidationError(msg)
return cleaned_data
def handle(self, request, data):
try:
LOG.debug('params = %s' % data)
params = {'name': data['name']}
params['gateway_ip'] = data['gateway_ip']
subnet = api.quantum.subnet_modify(request, data['subnet_id'],
name=data['name'],
gateway_ip=data['gateway_ip'])
msg = _('Subnet %s was successfully updated.') % data['cidr']
LOG.debug(msg)
messages.success(request, msg)
return subnet
except Exception:
msg = _('Failed to update subnet %s') % data['cidr']
LOG.info(msg)
redirect = reverse(self.failure_url, args=[data['network_id']])
exceptions.handle(request, msg, redirect=redirect)

View File

@ -22,26 +22,21 @@ import logging
from django.core.urlresolvers import reverse_lazy, reverse from django.core.urlresolvers import reverse_lazy, reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from horizon import forms
from horizon import exceptions from horizon import exceptions
from horizon import tabs from horizon import tabs
from horizon import workflows
from openstack_dashboard import api from openstack_dashboard import api
from .forms import CreateSubnet, UpdateSubnet
from .tabs import SubnetDetailTabs from .tabs import SubnetDetailTabs
from .workflows import CreateSubnet, UpdateSubnet
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class CreateView(forms.ModalFormView): class CreateView(workflows.WorkflowView):
form_class = CreateSubnet workflow_class = CreateSubnet
template_name = 'project/networks/subnets/create.html' template_name = 'project/networks/subnets/create.html'
success_url = 'horizon:project:networks:detail'
def get_success_url(self):
return reverse(self.success_url,
args=(self.kwargs['network_id'],))
def get_object(self): def get_object(self):
if not hasattr(self, "_object"): if not hasattr(self, "_object"):
@ -49,32 +44,22 @@ class CreateView(forms.ModalFormView):
network_id = self.kwargs["network_id"] network_id = self.kwargs["network_id"]
self._object = api.quantum.network_get(self.request, self._object = api.quantum.network_get(self.request,
network_id) network_id)
self._object.set_id_as_name_if_empty()
except: except:
redirect = reverse('horizon:project:networks:index') redirect = reverse('horizon:project:networks:index')
msg = _("Unable to retrieve network.") msg = _("Unable to retrieve network.")
exceptions.handle(self.request, msg, redirect=redirect) exceptions.handle(self.request, msg, redirect=redirect)
return self._object return self._object
def get_context_data(self, **kwargs):
context = super(CreateView, self).get_context_data(**kwargs)
context['network'] = self.get_object()
return context
def get_initial(self): def get_initial(self):
network = self.get_object() network = self.get_object()
return {"network_id": self.kwargs['network_id'], return {"network_id": self.kwargs['network_id'],
"network_name": network.name} "network_name": network.name}
class UpdateView(forms.ModalFormView): class UpdateView(workflows.WorkflowView):
form_class = UpdateSubnet workflow_class = UpdateSubnet
template_name = 'project/networks/subnets/update.html' template_name = 'project/networks/subnets/update.html'
context_object_name = 'subnet'
success_url = reverse_lazy('horizon:project:networks:detail')
def get_success_url(self):
return reverse('horizon:project:networks:detail',
args=(self.kwargs['network_id'],))
def _get_object(self, *args, **kwargs): def _get_object(self, *args, **kwargs):
if not hasattr(self, "_object"): if not hasattr(self, "_object"):
@ -87,23 +72,30 @@ class UpdateView(forms.ModalFormView):
exceptions.handle(self.request, msg, redirect=redirect) exceptions.handle(self.request, msg, redirect=redirect)
return self._object return self._object
def get_context_data(self, **kwargs):
context = super(UpdateView, self).get_context_data(**kwargs)
subnet = self._get_object()
context['subnet_id'] = subnet.id
context['network_id'] = subnet.network_id
context['cidr'] = subnet.cidr
context['ip_version'] = subnet.ipver_str
return context
def get_initial(self): def get_initial(self):
initial = super(UpdateView, self).get_initial()
subnet = self._get_object() subnet = self._get_object()
return {'network_id': self.kwargs['network_id'],
'subnet_id': subnet['id'], initial['network_id'] = self.kwargs['network_id']
'cidr': subnet['cidr'], initial['subnet_id'] = subnet['id']
'ip_version': subnet['ip_version'], initial['subnet_name'] = subnet['name']
'name': subnet['name'],
'gateway_ip': subnet['gateway_ip']} for key in ('cidr', 'ip_version', 'enable_dhcp'):
initial[key] = subnet[key]
initial['gateway_ip'] = subnet['gateway_ip'] or ''
initial['no_gateway'] = (subnet['gateway_ip'] is None)
initial['dns_nameservers'] = '\n'.join(subnet['dns_nameservers'])
pools = ['%s,%s' % (p['start'], p['end'])
for p in subnet['allocation_pools']]
initial['allocation_pools'] = '\n'.join(pools)
routes = ['%s,%s' % (r['destination'], r['nexthop'])
for r in subnet['host_routes']]
initial['host_routes'] = '\n'.join(routes)
return initial
class DetailView(tabs.TabView): class DetailView(tabs.TabView):

View File

@ -0,0 +1,198 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 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
import netaddr
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from horizon import exceptions
from horizon import forms
from horizon.utils import fields
from horizon import workflows
from openstack_dashboard import api
from openstack_dashboard.dashboards.project.networks import workflows \
as network_workflows
LOG = logging.getLogger(__name__)
class CreateSubnetInfoAction(network_workflows.CreateSubnetInfoAction):
with_subnet = forms.BooleanField(initial=True, required=False,
widget=forms.HiddenInput())
class Meta:
name = ("Subnet")
help_text = _('You can create a subnet associated with the '
'network. Advanced configuration are available '
'at "Subnet Detail" tab.')
def clean(self):
cleaned_data = workflows.Action.clean(self)
self._check_subnet_data(cleaned_data)
return cleaned_data
class CreateSubnetInfo(network_workflows.CreateSubnetInfo):
action_class = CreateSubnetInfoAction
depends_on = ("network_id",)
class CreateSubnet(network_workflows.CreateNetwork):
slug = "create_subnet"
name = _("Create Subnet")
finalize_button_name = _("Create")
success_message = _('Created subnet "%s".')
failure_message = _('Unable to create subnet "%s".')
default_steps = (CreateSubnetInfo,
network_workflows.CreateSubnetDetail)
def format_status_message(self, message):
name = self.context.get('subnet_name') or self.context.get('subnet_id')
return message % name
def get_success_url(self):
return reverse("horizon:project:networks:detail",
args=(self.context.get('network_id'),))
def get_failure_url(self):
return reverse("horizon:project:networks:detail",
args=(self.context.get('network_id'),))
def handle(self, request, data):
subnet = self._create_subnet(request, data)
return True if subnet else False
class UpdateSubnetInfoAction(CreateSubnetInfoAction):
cidr = fields.IPField(label=_("Network Address"),
required=False,
initial="",
widget=forms.TextInput(
attrs={'readonly': 'readonly'}),
help_text=_("Network address in CIDR format "
"(e.g. 192.168.0.0/24)"),
version=fields.IPv4 | fields.IPv6,
mask=True)
# NOTE(amotoki): When 'disabled' attribute is set for the ChoiceField
# and ValidationError is raised for POST request, the initial value of
# the ip_version ChoiceField is not set in the re-displayed form
# As a result, 'IPv4' is displayed even when IPv6 is used if
# ValidationError is detected. In addition 'required=True' check complains
# when re-POST since the value of the ChoiceField is not set.
# Thus now I use HiddenInput for the ip_version ChoiceField as a work
# around.
ip_version = forms.ChoiceField(choices=[(4, 'IPv4'), (6, 'IPv6')],
#widget=forms.Select(
# attrs={'disabled': 'disabled'}),
widget=forms.HiddenInput(),
label=_("IP Version"))
gateway_ip = fields.IPField(
label=_("Gateway IP (optional)"),
required=False,
initial="",
help_text=_("IP address of Gateway (e.g. 192.168.0.254). "
"You need to specify an explicit address "
"to set the gateway. "
"If you want to use no gateway, "
"check 'Disable Gateway' below."),
version=fields.IPv4 | fields.IPv6,
mask=False)
no_gateway = forms.BooleanField(label=_("Disable Gateway"),
initial=False, required=False)
class Meta:
name = ("Subnet")
help_text = _('You can update a subnet associated with the '
'network. Advanced configuration are available '
'at "Subnet Detail" tab.')
def clean(self):
cleaned_data = workflows.Action.clean(self)
self._check_subnet_data(cleaned_data, is_create=False)
return cleaned_data
class UpdateSubnetInfo(CreateSubnetInfo):
action_class = UpdateSubnetInfoAction
depends_on = ("network_id", "subnet_id")
class UpdateSubnetDetailAction(network_workflows.CreateSubnetDetailAction):
allocation_pools = forms.CharField(widget=forms.HiddenInput(),
required=False)
class Meta:
name = ("Subnet Detail")
help_text = _('You can specify additional attributes for the subnet.')
class UpdateSubnetDetail(network_workflows.CreateSubnetDetail):
action_class = UpdateSubnetDetailAction
class UpdateSubnet(network_workflows.CreateNetwork):
slug = "update_subnet"
name = _("Update Subnet")
finalize_button_name = _("Update")
success_message = _('Updated subnet "%s".')
failure_message = _('Unable to update subnet "%s".')
success_url = "horizon:project:networks:detail"
failure_url = "horizon:project:networks:detail"
default_steps = (UpdateSubnetInfo,
UpdateSubnetDetail)
def format_status_message(self, message):
name = self.context.get('subnet_name') or self.context.get('subnet_id')
return message % name
def get_success_url(self):
return reverse(self.success_url,
args=(self.context.get('network_id'),))
def _update_subnet(self, request, data):
network_id = self.context.get('network_id')
try:
subnet_id = self.context.get('subnet_id')
params = {}
params['name'] = data['subnet_name']
if data['no_gateway']:
params['gateway_ip'] = None
elif data['gateway_ip']:
params['gateway_ip'] = data['gateway_ip']
self._setup_subnet_parameters(params, data, is_create=False)
subnet = api.quantum.subnet_modify(request, subnet_id, **params)
msg = _('Subnet "%s" was successfully updated.') % data['cidr']
LOG.debug(msg)
return subnet
except Exception as e:
msg = (_('Failed to update subnet "%(sub)s": '
' %(reason)s') %
{"sub": data['cidr'], "reason": e})
redirect = reverse(self.failure_url, args=(network_id,))
exceptions.handle(request, msg, redirect=redirect)
return False
def handle(self, request, data):
subnet = self._update_subnet(request, data)
return True if subnet else False

View File

@ -16,5 +16,13 @@
<dd>{{ network.admin_state|default:"Unknown" }}</dd> <dd>{{ network.admin_state|default:"Unknown" }}</dd>
<dt>{% trans "Shared" %}</dt> <dt>{% trans "Shared" %}</dt>
<dd>{{ network.shared|yesno|capfirst }}</dd> <dd>{{ network.shared|yesno|capfirst }}</dd>
<dt>{% trans "External Network" %}</dt>
<dd>{{ network.router__external|yesno|capfirst }}</dd>
{% if network.provider__network_type %}
<dt>{% trans "Provider Network" %}</dt>
<dd>{% trans "Network Type" %}: {{ network.provider__network_type|default:"Unknown" }}</dd>
<dd>{% trans "Physical Network" %}: {{ network.provider__physical_network|default:"-" }}</dd>
<dd>{% trans "Segmentation ID" %}: {{ network.provider__segmentation_id|default:"-" }}</dd>
{% endif %}
</dl> </dl>
</div> </div>

View File

@ -10,8 +10,9 @@
<dd>{{ port.name|default:"None" }}</dd> <dd>{{ port.name|default:"None" }}</dd>
<dt>{% trans "ID" %}</dt> <dt>{% trans "ID" %}</dt>
<dd>{{ port.id|default:"None" }}</dd> <dd>{{ port.id|default:"None" }}</dd>
{% url horizon:project:networks:detail port.network_id as network_url %}
<dt>{% trans "Network ID" %}</dt> <dt>{% trans "Network ID" %}</dt>
<dd>{{ port.network_id|default:"None" }}</dd> <dd><a href="{{ network_url }}">{{ port.network_id|default:"None" }}</a></dd>
<dt>{% trans "Project ID" %}</dt> <dt>{% trans "Project ID" %}</dt>
<dd>{{ port.tenant_id|default:"-" }}</dd> <dd>{{ port.tenant_id|default:"-" }}</dd>
<dt>{% trans "Fixed IP" %}</dt> <dt>{% trans "Fixed IP" %}</dt>
@ -31,9 +32,10 @@
<dd>{{ port.status|default:"None" }}</dd> <dd>{{ port.status|default:"None" }}</dd>
<dt>{% trans "Admin State" %}</dt> <dt>{% trans "Admin State" %}</dt>
<dd>{{ port.admin_state|default:"None" }}</dd> <dd>{{ port.admin_state|default:"None" }}</dd>
<dt>{% trans "Device ID" %}</dt> <dt>{% trans "Attached Device" %}</dt>
{% if port.device_id|length > 1 %} {% if port.device_id|length > 1 or port.device_owner %}
<dd>{{ port.device_id }}</dd> <dd><b>{% trans "Device Owner" %}</b>: {{ port.device_owner|default:"None" }}</dd>
<dd><b>{% trans "Device ID" %}</b>: {{ port.device_id|default:"-" }}</dd>
{% else %} {% else %}
<dd>No attached device</dd> <dd>No attached device</dd>
{% endif %} {% endif %}

View File

@ -1,20 +1,16 @@
{% extends "horizon/common/_modal_form.html" %} {% extends "horizon/common/_modal_form.html" %}
{% load i18n %} {% load i18n %}
{% block form_id %}update_subnet_form{% endblock %} {% block form_id %}update_port_form{% endblock %}
{% block form_action %}{% url horizon:project:networks:editsubnet network_id subnet_id %}{% endblock %} {% block form_action %}{% url horizon:project:networks:editport network_id port_id %}{% endblock %}
{% block modal-header %}{% trans "Edit Subnet" %}{% endblock %} {% block modal-header %}{% trans "Edit Port" %}{% endblock %}
{% block modal-body %} {% block modal-body %}
<div class="left"> <div class="left">
<dl> <dl>
<dt>{% trans "ID" %}</dt> <dt>{% trans "ID" %}</dt>
<dd>{{ subnet_id }}</dd> <dd>{{ port_id }}</dd>
<dt>{% trans "Network Address" %}</dt>
<dd>{{ cidr }}</dd>
<dt>{% trans "IP version" %}</dt>
<dd>{{ ip_version }}</dd>
</dl> </dl>
<hr> <hr>
<fieldset> <fieldset>
@ -23,7 +19,7 @@
</div> </div>
<div class="right"> <div class="right">
<h3>{% trans "Description:" %}</h3> <h3>{% trans "Description:" %}</h3>
<p>{% trans "You may update the editable properties of your subnet here." %}</p> <p>{% trans "You may update the editable properties of your port here." %}</p>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Update Port" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Update Port") %}
{% endblock page_header %}
{% block main %}
{% include 'project/networks/ports/_update.html' %}
{% endblock %}

View File

@ -1,25 +0,0 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block form_id %}create_subnet_form{% endblock %}
{% block form_action %}{% url horizon:project:networks:addsubnet network.id %}
{% endblock %}
{% block modal-header %}{% trans "Create Subnet" %}{% 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 create a subnet for the network. Any network address can be specified unless the network address does not overlap other subnets in the network." %}</p>
</div>
{% endblock %}
{% block modal-footer %}
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Subnet" %}" />
<a href="{% url horizon:project:networks:index %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
{% endblock %}

View File

@ -10,14 +10,13 @@
<dd>{{ subnet.name|default:"None" }}</dd> <dd>{{ subnet.name|default:"None" }}</dd>
<dt>{% trans "ID" %}</dt> <dt>{% trans "ID" %}</dt>
<dd>{{ subnet.id|default:"None" }}</dd> <dd>{{ subnet.id|default:"None" }}</dd>
{% url horizon:project:networks:detail subnet.network_id as network_url %}
<dt>{% trans "Network ID" %}</dt> <dt>{% trans "Network ID" %}</dt>
<dd>{{ subnet.network_id|default:"None" }}</dd> <dd><a href="{{ network_url }}">{{ subnet.network_id|default:"None" }}</a></dd>
<dt>{% trans "CIDR" %}</dt>
<dd>{{ subnet.cidr|default:"None" }}</dd>
<dt>{% trans "IP version" %}</dt> <dt>{% trans "IP version" %}</dt>
<dd>{{ subnet.ipver_str|default:"-" }}</dd> <dd>{{ subnet.ipver_str|default:"-" }}</dd>
<dt>{% trans "Gateway IP" %}</dt> <dt>{% trans "CIDR" %}</dt>
<dd>{{ subnet.gateway_ip|default:"-" }}</dd> <dd>{{ subnet.cidr|default:"None" }}</dd>
<dt>{% trans "IP allocation pool" %}</dt> <dt>{% trans "IP allocation pool" %}</dt>
<dd> <dd>
{% for pool in subnet.allocation_pools %} {% for pool in subnet.allocation_pools %}
@ -25,5 +24,26 @@
{% trans " - End" %} {{ pool.end }}<br> {% trans " - End" %} {{ pool.end }}<br>
{% endfor %} {% endfor %}
</dd> </dd>
<dt>{% trans "DHCP Enable" %}<dt>
<dd>{{ subnet.enable_dhcp|yesno|capfirst }}</dd>
<dt>{% trans "Gateway IP" %}</dt>
<dd>{{ subnet.gateway_ip|default:"-" }}</dd>
<dt>{% trans "Additional routes" %}</dt>
<dd>
{% for route in subnet.host_routes %}
{% trans "Destination" %} {{ route.destination }}
{% trans " : Next hop" %} {{ route.nexthop }}<br>
{% empty %}
{% trans "None" %}
{% endfor %}
</dd>
<dt>{% trans "DNS name server" %}</dt>
<dd>
{% for dns in subnet.dns_nameservers %}
{{ dns }}
{% empty %}
{% trans "None" %}
{% endfor %}
</dd>
</dl> </dl>
</div> </div>

View File

@ -7,5 +7,5 @@
{% endblock page_header %} {% endblock page_header %}
{% block main %} {% block main %}
{% include "project/networks/subnets/_create.html" %} {% include "horizon/common/_workflow.html" %}
{% endblock %} {% endblock %}

View File

@ -7,5 +7,5 @@
{% endblock page_header %} {% endblock page_header %}
{% block main %} {% block main %}
{% include 'project/networks/subnets/_update.html' %} {% include "horizon/common/_workflow.html" %}
{% endblock %} {% endblock %}

File diff suppressed because it is too large Load Diff

View File

@ -20,6 +20,7 @@ from .views import IndexView, CreateView, DetailView, UpdateView
from .subnets.views import CreateView as AddSubnetView from .subnets.views import CreateView as AddSubnetView
from .subnets.views import UpdateView as EditSubnetView from .subnets.views import UpdateView as EditSubnetView
from .subnets import urls as subnet_urls from .subnets import urls as subnet_urls
from .ports.views import UpdateView as EditPortView
from .ports import urls as port_urls from .ports import urls as port_urls
@ -35,5 +36,7 @@ urlpatterns = patterns('',
name='addsubnet'), name='addsubnet'),
url(r'^(?P<network_id>[^/]+)/subnets/(?P<subnet_id>[^/]+)/update$', url(r'^(?P<network_id>[^/]+)/subnets/(?P<subnet_id>[^/]+)/update$',
EditSubnetView.as_view(), name='editsubnet'), EditSubnetView.as_view(), name='editsubnet'),
url(r'^(?P<network_id>[^/]+)/ports/(?P<port_id>[^/]+)/update$',
EditPortView.as_view(), name='editport'),
url(r'^subnets/', include(subnet_urls, namespace='subnets')), url(r'^subnets/', include(subnet_urls, namespace='subnets')),
url(r'^ports/', include(port_urls, namespace='ports'))) url(r'^ports/', include(port_urls, namespace='ports')))

View File

@ -91,7 +91,8 @@ class UpdateView(forms.ModalFormView):
network = self._get_object() network = self._get_object()
return {'network_id': network['id'], return {'network_id': network['id'],
'tenant_id': network['tenant_id'], 'tenant_id': network['tenant_id'],
'name': network['name']} 'name': network['name'],
'admin_state': network['admin_state_up']}
class DetailView(tables.MultiTableView): class DetailView(tables.MultiTableView):

View File

@ -23,6 +23,7 @@ from django.utils.translation import ugettext as _
from horizon import exceptions from horizon import exceptions
from horizon import forms from horizon import forms
from horizon import messages
from horizon import workflows from horizon import workflows
from horizon.utils import fields from horizon.utils import fields
@ -34,8 +35,12 @@ LOG = logging.getLogger(__name__)
class CreateNetworkInfoAction(workflows.Action): class CreateNetworkInfoAction(workflows.Action):
net_name = forms.CharField(max_length=255, net_name = forms.CharField(max_length=255,
label=_("Network Name (optional)"), label=_("Network Name"),
help_text=_("Network Name. This field is "
"optional."),
required=False) required=False)
admin_state = forms.BooleanField(label=_("Admin State"),
initial=True, required=False)
class Meta: class Meta:
name = ("Network") name = ("Network")
@ -46,14 +51,16 @@ class CreateNetworkInfoAction(workflows.Action):
class CreateNetworkInfo(workflows.Step): class CreateNetworkInfo(workflows.Step):
action_class = CreateNetworkInfoAction action_class = CreateNetworkInfoAction
contributes = ("net_name",) contributes = ("net_name", "admin_state")
class CreateSubnetInfoAction(workflows.Action): class CreateSubnetInfoAction(workflows.Action):
with_subnet = forms.BooleanField(label=_("Create Subnet"), with_subnet = forms.BooleanField(label=_("Create Subnet"),
initial=True, required=False) initial=True, required=False)
subnet_name = forms.CharField(max_length=255, subnet_name = forms.CharField(max_length=255,
label=_("Subnet Name (optional)"), label=_("Subnet Name"),
help_text=_("Subnet Name. This field is "
"optional."),
required=False) required=False)
cidr = fields.IPField(label=_("Network Address"), cidr = fields.IPField(label=_("Network Address"),
required=False, required=False,
@ -64,13 +71,21 @@ class CreateSubnetInfoAction(workflows.Action):
mask=True) mask=True)
ip_version = forms.ChoiceField(choices=[(4, 'IPv4'), (6, 'IPv6')], ip_version = forms.ChoiceField(choices=[(4, 'IPv4'), (6, 'IPv6')],
label=_("IP Version")) label=_("IP Version"))
gateway_ip = fields.IPField(label=_("Gateway IP (optional)"), gateway_ip = fields.IPField(
required=False, label=_("Gateway IP (optional)"),
initial="", required=False,
help_text=_("IP address of Gateway " initial="",
"(e.g. 192.168.0.1)"), help_text=_("IP address of Gateway (e.g. 192.168.0.254) "
version=fields.IPv4 | fields.IPv6, "The default value is the first IP of the "
mask=False) "network address (e.g. 192.168.0.1 for "
"192.168.0.0/24). "
"If you use the default, leave blank. "
"If you want to use no gateway, "
"check 'Disable Gateway' below."),
version=fields.IPv4 | fields.IPv6,
mask=False)
no_gateway = forms.BooleanField(label=_("Disable Gateway"),
initial=False, required=False)
class Meta: class Meta:
name = ("Subnet") name = ("Subnet")
@ -79,13 +94,12 @@ class CreateSubnetInfoAction(workflows.Action):
'specified. If you wish to create a network WITHOUT a ' 'specified. If you wish to create a network WITHOUT a '
'subnet, uncheck the "Create Subnet" checkbox.') 'subnet, uncheck the "Create Subnet" checkbox.')
def clean(self): def _check_subnet_data(self, cleaned_data, is_create=True):
cleaned_data = super(CreateSubnetInfoAction, self).clean()
with_subnet = cleaned_data.get('with_subnet')
cidr = cleaned_data.get('cidr') cidr = cleaned_data.get('cidr')
ip_version = int(cleaned_data.get('ip_version')) ip_version = int(cleaned_data.get('ip_version'))
gateway_ip = cleaned_data.get('gateway_ip') gateway_ip = cleaned_data.get('gateway_ip')
if with_subnet and not cidr: no_gateway = cleaned_data.get('no_gateway')
if not cidr:
msg = _('Specify "Network Address" or ' msg = _('Specify "Network Address" or '
'clear "Create Subnet" checkbox.') 'clear "Create Subnet" checkbox.')
raise forms.ValidationError(msg) raise forms.ValidationError(msg)
@ -93,17 +107,126 @@ class CreateSubnetInfoAction(workflows.Action):
if netaddr.IPNetwork(cidr).version is not ip_version: if netaddr.IPNetwork(cidr).version is not ip_version:
msg = _('Network Address and IP version are inconsistent.') msg = _('Network Address and IP version are inconsistent.')
raise forms.ValidationError(msg) raise forms.ValidationError(msg)
if gateway_ip: if not no_gateway and gateway_ip:
if netaddr.IPAddress(gateway_ip).version is not ip_version: if netaddr.IPAddress(gateway_ip).version is not ip_version:
msg = _('Gateway IP and IP version are inconsistent.') msg = _('Gateway IP and IP version are inconsistent.')
raise forms.ValidationError(msg) raise forms.ValidationError(msg)
if not is_create and not no_gateway and not gateway_ip:
msg = _('Specify IP address of gateway or '
'check "Disable Gateway".')
raise forms.ValidationError(msg)
def clean(self):
cleaned_data = super(CreateSubnetInfoAction, self).clean()
with_subnet = cleaned_data.get('with_subnet')
if not with_subnet:
return cleaned_data
self._check_subnet_data(cleaned_data)
return cleaned_data return cleaned_data
class CreateSubnetInfo(workflows.Step): class CreateSubnetInfo(workflows.Step):
action_class = CreateSubnetInfoAction action_class = CreateSubnetInfoAction
contributes = ("with_subnet", "subnet_name", "cidr", contributes = ("with_subnet", "subnet_name", "cidr",
"ip_version", "gateway_ip") "ip_version", "gateway_ip", "no_gateway")
class CreateSubnetDetailAction(workflows.Action):
enable_dhcp = forms.BooleanField(label=_("Enable DHCP"),
initial=True, required=False)
allocation_pools = forms.CharField(
widget=forms.Textarea(),
label=_("Allocation Pools"),
help_text=_("IP address allocation pools. Each entry is "
"&lt;start_ip_address&gt;,&lt;end_ip_address&gt; "
"(e.g., 192.168.1.100,192.168.1.120) "
"and one entry per line."),
required=False)
dns_nameservers = forms.CharField(
widget=forms.widgets.Textarea(),
label=_("DNS Name Servers"),
help_text=_("IP address list of DNS name servers for this subnet. "
"One entry per line."),
required=False)
host_routes = forms.CharField(
widget=forms.widgets.Textarea(),
label=_("Host Routes"),
help_text=_("Additional routes announced to the hosts. "
"Each entry is &lt;destination_cidr&gt;,&lt;nexthop&gt; "
"(e.g., 192.168.200.0/24,10.56.1.254)"
"and one entry per line."),
required=False)
class Meta:
name = ("Subnet Detail")
help_text = _('You can specify additional attributes for the subnet.')
def _convert_ip_address(self, ip, field_name):
try:
return netaddr.IPAddress(ip)
except (netaddr.AddrFormatError, ValueError):
msg = _('%(field_name)s: Invalid IP address '
'(value=%(ip)s)') % locals()
raise forms.ValidationError(msg)
def _convert_ip_network(self, network, field_name):
try:
return netaddr.IPNetwork(network)
except (netaddr.AddrFormatError, ValueError):
msg = _('%(field_name)s: Invalid IP address '
'(value=%(network)s)') % locals()
raise forms.ValidationError(msg)
def _check_allocation_pools(self, allocation_pools):
for p in allocation_pools.split('\n'):
p = p.strip()
if not p:
continue
pool = p.split(',')
if len(pool) != 2:
msg = _('Start and end addresses must be specified '
'(value=%s)') % p
raise forms.ValidationError(msg)
start, end = [self._convert_ip_address(ip, "allocation_pools")
for ip in pool]
if start > end:
msg = _('Start address is larger than end address '
'(value=%s)') % p
raise forms.ValidationError(msg)
def _check_dns_nameservers(self, dns_nameservers):
for ns in dns_nameservers.split('\n'):
ns = ns.strip()
if not ns:
continue
self._convert_ip_address(ns, "dns_nameservers")
def _check_host_routes(self, host_routes):
for r in host_routes.split('\n'):
r = r.strip()
if not r:
continue
route = r.split(',')
if len(route) != 2:
msg = _('Host Routes format error: '
'Destination CIDR and nexthop must be specified '
'(value=%s)') % r
raise forms.ValidationError(msg)
dest = self._convert_ip_network(route[0], "host_routes")
nexthop = self._convert_ip_address(route[1], "host_routes")
def clean(self):
cleaned_data = super(CreateSubnetDetailAction, self).clean()
self._check_allocation_pools(cleaned_data.get('allocation_pools'))
self._check_host_routes(cleaned_data.get('host_routes'))
self._check_dns_nameservers(cleaned_data.get('dns_nameservers'))
return cleaned_data
class CreateSubnetDetail(workflows.Step):
action_class = CreateSubnetDetailAction
contributes = ("enable_dhcp", "allocation_pools",
"dns_nameservers", "host_routes")
class CreateNetwork(workflows.Workflow): class CreateNetwork(workflows.Workflow):
@ -112,51 +235,130 @@ class CreateNetwork(workflows.Workflow):
finalize_button_name = _("Create") finalize_button_name = _("Create")
success_message = _('Created network "%s".') success_message = _('Created network "%s".')
failure_message = _('Unable to create network "%s".') failure_message = _('Unable to create network "%s".')
success_url = "horizon:project:networks:index"
default_steps = (CreateNetworkInfo, default_steps = (CreateNetworkInfo,
CreateSubnetInfo) CreateSubnetInfo,
CreateSubnetDetail)
def get_success_url(self):
return reverse("horizon:project:networks:index")
def get_failure_url(self):
return reverse("horizon:project:networks:index")
def format_status_message(self, message): def format_status_message(self, message):
name = self.context.get('net_name') or self.context.get('net_id', '') name = self.context.get('net_name') or self.context.get('net_id', '')
return message % name return message % name
def handle(self, request, data): def _create_network(self, request, data):
# create the network
try: try:
network = api.quantum.network_create(request, params = {'name': data['net_name'],
name=data['net_name']) 'admin_state_up': data['admin_state']}
network = api.quantum.network_create(request, **params)
network.set_id_as_name_if_empty() network.set_id_as_name_if_empty()
self.context['net_id'] = network.id self.context['net_id'] = network.id
msg = _('Network "%s" was successfully created.') % network.name msg = _('Network "%s" was successfully created.') % network.name
LOG.debug(msg) LOG.debug(msg)
except: return network
msg = _('Failed to create network "%s".') % data['net_name'] except Exception as e:
msg = (_('Failed to create network "%(network)s": %(reason)s') %
{"network": data['net_name'], "reason": e})
LOG.info(msg) LOG.info(msg)
redirect = reverse('horizon:project:networks:index') redirect = self.get_failure_url()
exceptions.handle(request, msg, redirect=redirect) exceptions.handle(request, msg, redirect=redirect)
return False return False
# If we do not need to create a subnet, return here. def _setup_subnet_parameters(self, params, data, is_create=True):
if not data['with_subnet']: """Setup subnet parameters
return True
# Create the subnet. This methods setups subnet parameters which are available
in both create and update.
"""
is_update = not is_create
params['enable_dhcp'] = data['enable_dhcp']
if is_create and data['allocation_pools']:
pools = [dict(zip(['start', 'end'], pool.strip().split(',')))
for pool in data['allocation_pools'].split('\n')
if pool.strip()]
params['allocation_pools'] = pools
if data['host_routes'] or is_update:
routes = [dict(zip(['destination', 'nexthop'],
route.strip().split(',')))
for route in data['host_routes'].split('\n')
if route.strip()]
params['host_routes'] = routes
if data['dns_nameservers'] or is_update:
nameservers = [ns.strip()
for ns in data['dns_nameservers'].split('\n')
if ns.strip()]
params['dns_nameservers'] = nameservers
def _create_subnet(self, request, data, network=None, tenant_id=None,
no_redirect=False):
if network:
network_id = network.id
network_name = network.name
else:
network_id = self.context.get('network_id')
network_name = self.context.get('network_name')
try: try:
params = {'network_id': network.id, params = {'network_id': network_id,
'name': data['subnet_name'], 'name': data['subnet_name'],
'cidr': data['cidr'], 'cidr': data['cidr'],
'ip_version': int(data['ip_version'])} 'ip_version': int(data['ip_version'])}
if data['gateway_ip']: if tenant_id:
params['tenant_id'] = tenant_id
if data['no_gateway']:
params['gateway_ip'] = None
elif data['gateway_ip']:
params['gateway_ip'] = data['gateway_ip'] params['gateway_ip'] = data['gateway_ip']
api.quantum.subnet_create(request, **params)
self._setup_subnet_parameters(params, data)
subnet = api.quantum.subnet_create(request, **params)
self.context['subnet_id'] = subnet.id
msg = _('Subnet "%s" was successfully created.') % data['cidr'] msg = _('Subnet "%s" was successfully created.') % data['cidr']
LOG.debug(msg) LOG.debug(msg)
except Exception: return subnet
msg = _('Failed to create subnet "%(sub)s" for network "%(net)s".') except Exception as e:
redirect = reverse('horizon:project:networks:index') msg = _('Failed to create subnet "%(sub)s" for network "%(net)s": '
' %(reason)s')
if no_redirect:
redirect = None
else:
redirect = self.get_failure_url()
exceptions.handle(request, exceptions.handle(request,
msg % {"sub": data['cidr'], "net": network.id}, msg % {"sub": data['cidr'], "net": network_name,
"reason": e},
redirect=redirect) redirect=redirect)
return False return False
return True def _delete_network(self, request, network):
"""Delete the created network when subnet creation failed"""
try:
api.quantum.network_delete(request, network.id)
msg = _('Delete the created network "%s" '
'due to subnet creation failure.') % network.name
LOG.debug(msg)
redirect = self.get_failure_url()
messages.info(request, msg)
raise exceptions.Http302(redirect)
#return exceptions.RecoverableError
except:
msg = _('Failed to delete network "%s"') % network.name
LOG.info(msg)
redirect = self.get_failure_url()
exceptions.handle(request, msg, redirect=redirect)
def handle(self, request, data):
network = self._create_network(request, data)
if not network:
return False
# If we do not need to create a subnet, return here.
if not data['with_subnet']:
return True
subnet = self._create_subnet(request, data, network, no_redirect=True)
if subnet:
return True
else:
self._delete_network(request, network)
return False

View File

@ -37,9 +37,12 @@ def data(TEST):
'status': 'ACTIVE', 'status': 'ACTIVE',
'subnets': ['e8abc972-eb0c-41f1-9edd-4bc6e3bcd8c9'], 'subnets': ['e8abc972-eb0c-41f1-9edd-4bc6e3bcd8c9'],
'tenant_id': '1', 'tenant_id': '1',
'router:external': False,
'shared': False} 'shared': False}
subnet_dict = {'allocation_pools': [{'end': '10.0.0.254', subnet_dict = {'allocation_pools': [{'end': '10.0.0.254',
'start': '10.0.0.2'}], 'start': '10.0.0.2'}],
'dns_nameservers': [],
'host_routes': [],
'cidr': '10.0.0.0/24', 'cidr': '10.0.0.0/24',
'enable_dhcp': True, 'enable_dhcp': True,
'gateway_ip': '10.0.0.1', 'gateway_ip': '10.0.0.1',
@ -50,6 +53,7 @@ def data(TEST):
'tenant_id': network_dict['tenant_id']} 'tenant_id': network_dict['tenant_id']}
port_dict = {'admin_state_up': True, port_dict = {'admin_state_up': True,
'device_id': 'af75c8e5-a1cc-4567-8d04-44fcd6922890', 'device_id': 'af75c8e5-a1cc-4567-8d04-44fcd6922890',
'device_owner': 'network:dhcp',
'fixed_ips': [{'ip_address': '10.0.0.3', 'fixed_ips': [{'ip_address': '10.0.0.3',
'subnet_id': subnet_dict['id']}], 'subnet_id': subnet_dict['id']}],
'id': '3ec7f3db-cb2f-4a34-ab6b-69a64d3f008c', 'id': '3ec7f3db-cb2f-4a34-ab6b-69a64d3f008c',
@ -77,9 +81,15 @@ def data(TEST):
'status': 'ACTIVE', 'status': 'ACTIVE',
'subnets': ['3f7c5d79-ee55-47b0-9213-8e669fb03009'], 'subnets': ['3f7c5d79-ee55-47b0-9213-8e669fb03009'],
'tenant_id': '2', 'tenant_id': '2',
'router:external': True,
'shared': True} 'shared': True}
subnet_dict = {'allocation_pools': [{'end': '172.16.88.254', subnet_dict = {'allocation_pools': [{'end': '172.16.88.254',
'start': '172.16.88.2'}], 'start': '172.16.88.2'}],
'dns_nameservers': ['10.56.1.20', '10.56.1.21'],
'host_routes': [{'destination': '192.168.20.0/24',
'nexthop': '172.16.88.253'},
{'destination': '192.168.21.0/24',
'nexthop': '172.16.88.252'}],
'cidr': '172.16.88.0/24', 'cidr': '172.16.88.0/24',
'enable_dhcp': True, 'enable_dhcp': True,
'gateway_ip': '172.16.88.1', 'gateway_ip': '172.16.88.1',
@ -90,6 +100,7 @@ def data(TEST):
'tenant_id': network_dict['tenant_id']} 'tenant_id': network_dict['tenant_id']}
port_dict = {'admin_state_up': True, port_dict = {'admin_state_up': True,
'device_id': '40e536b1-b9fd-4eb7-82d6-84db5d65a2ac', 'device_id': '40e536b1-b9fd-4eb7-82d6-84db5d65a2ac',
'device_owner': 'compute:nova',
'fixed_ips': [{'ip_address': '172.16.88.3', 'fixed_ips': [{'ip_address': '172.16.88.3',
'subnet_id': subnet_dict['id']}], 'subnet_id': subnet_dict['id']}],
'id': '7e6ce62c-7ea2-44f8-b6b4-769af90a8406', 'id': '7e6ce62c-7ea2-44f8-b6b4-769af90a8406',