diff --git a/openstack_dashboard/api/quantum.py b/openstack_dashboard/api/quantum.py
index 22c86271b..2c2f26e28 100644
--- a/openstack_dashboard/api/quantum.py
+++ b/openstack_dashboard/api/quantum.py
@@ -50,19 +50,19 @@ class QuantumAPIDictWrapper(APIDictWrapper):
class Network(QuantumAPIDictWrapper):
"""Wrapper for quantum Networks"""
- _attrs = ['name', 'id', 'subnets', 'tenant_id', 'status',
- 'admin_state_up', 'shared']
def __init__(self, apiresource):
apiresource['admin_state'] = \
'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)
class Subnet(QuantumAPIDictWrapper):
"""Wrapper for quantum subnets"""
- _attrs = ['name', 'id', 'cidr', 'network_id', 'tenant_id',
- 'ip_version', 'ipver_str']
def __init__(self, apiresource):
apiresource['ipver_str'] = get_ipver_str(apiresource['ip_version'])
@@ -71,9 +71,6 @@ class Subnet(QuantumAPIDictWrapper):
class Port(QuantumAPIDictWrapper):
"""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):
apiresource['admin_state'] = \
diff --git a/openstack_dashboard/dashboards/admin/networks/forms.py b/openstack_dashboard/dashboards/admin/networks/forms.py
index 213f00ea5..13f177e14 100644
--- a/openstack_dashboard/dashboards/admin/networks/forms.py
+++ b/openstack_dashboard/dashboards/admin/networks/forms.py
@@ -34,8 +34,12 @@ class CreateNetwork(forms.SelfHandlingForm):
label=_("Name"),
required=False)
tenant_id = forms.ChoiceField(label=_("Project"))
+ admin_state = forms.BooleanField(label=_("Admin State"),
+ initial=True, required=False)
shared = forms.BooleanField(label=_("Shared"),
initial=False, required=False)
+ external = forms.BooleanField(label=_("External Network"),
+ initial=False, required=False)
@classmethod
def _instantiate(cls, request, *args, **kwargs):
@@ -51,10 +55,12 @@ class CreateNetwork(forms.SelfHandlingForm):
def handle(self, request, data):
try:
- network = api.quantum.network_create(request,
- name=data['name'],
- tenant_id=data['tenant_id'],
- shared=data['shared'])
+ params = {'name': data['name'],
+ 'tenant_id': data['tenant_id'],
+ 'admin_state_up': data['admin_state'],
+ 'shared': data['shared'],
+ 'router:external': data['external']}
+ network = api.quantum.network_create(request, **params)
msg = _('Network %s was successfully created.') % data['name']
LOG.debug(msg)
messages.success(request, msg)
@@ -71,14 +77,19 @@ class UpdateNetwork(forms.SelfHandlingForm):
network_id = forms.CharField(label=_("ID"),
widget=forms.TextInput(
attrs={'readonly': 'readonly'}))
+ admin_state = forms.BooleanField(label=_("Admin State"), required=False)
shared = forms.BooleanField(label=_("Shared"), required=False)
+ external = forms.BooleanField(label=_("External Network"), required=False)
failure_url = 'horizon:admin:networks:index'
def handle(self, request, data):
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'],
- name=data['name'],
- shared=data['shared'])
+ **params)
msg = _('Network %s was successfully updated.') % data['name']
LOG.debug(msg)
messages.success(request, msg)
diff --git a/openstack_dashboard/dashboards/admin/networks/ports/forms.py b/openstack_dashboard/dashboards/admin/networks/ports/forms.py
index 19d0838cd..7a1f45d43 100644
--- a/openstack_dashboard/dashboards/admin/networks/ports/forms.py
+++ b/openstack_dashboard/dashboards/admin/networks/ports/forms.py
@@ -24,6 +24,8 @@ from horizon import forms
from horizon import messages
from openstack_dashboard import api
+from openstack_dashboard.dashboards.project.networks.ports \
+ import forms as project_forms
LOG = logging.getLogger(__name__)
@@ -39,9 +41,14 @@ class CreatePort(forms.SelfHandlingForm):
name = forms.CharField(max_length=255,
label=_("Name"),
required=False)
+ admin_state = forms.BooleanField(label=_("Admin State"),
+ initial=True, required=False)
device_id = forms.CharField(max_length=100, label=_("Device ID"),
help_text='Device ID attached to the port',
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):
try:
@@ -49,6 +56,8 @@ class CreatePort(forms.SelfHandlingForm):
# 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
+ data['admin_state_up'] = data['admin_state']
+ del data['admin_state']
port = api.quantum.port_create(request, **data)
msg = _('Port %s was successfully created.') % port['id']
@@ -64,23 +73,24 @@ class CreatePort(forms.SelfHandlingForm):
exceptions.handle(request, msg, redirect=redirect)
-class UpdatePort(forms.SelfHandlingForm):
- network_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)
+class UpdatePort(project_forms.UpdatePort):
+ #tenant_id = forms.CharField(widget=forms.HiddenInput())
device_id = forms.CharField(max_length=100, label=_("Device ID"),
help_text='Device ID attached to the port',
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):
try:
LOG.debug('params = %s' % data)
port = api.quantum.port_modify(request, data['port_id'],
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']
LOG.debug(msg)
messages.success(request, msg)
@@ -88,6 +98,6 @@ class UpdatePort(forms.SelfHandlingForm):
except Exception:
msg = _('Failed to update port %s') % data['port_id']
LOG.info(msg)
- redirect = reverse('horizon:admin:networks:detail',
+ redirect = reverse(self.failure_url,
args=[data['network_id']])
exceptions.handle(request, msg, redirect=redirect)
diff --git a/openstack_dashboard/dashboards/admin/networks/ports/views.py b/openstack_dashboard/dashboards/admin/networks/ports/views.py
index 43fad6390..384670d36 100644
--- a/openstack_dashboard/dashboards/admin/networks/ports/views.py
+++ b/openstack_dashboard/dashboards/admin/networks/ports/views.py
@@ -23,6 +23,9 @@ from horizon import exceptions
from horizon import forms
from openstack_dashboard import api
+from openstack_dashboard.dashboards.project.networks.ports \
+ import views as project_views
+
from .forms import CreatePort, UpdatePort
LOG = logging.getLogger(__name__)
@@ -32,6 +35,7 @@ class CreateView(forms.ModalFormView):
form_class = CreatePort
template_name = 'admin/networks/ports/create.html'
success_url = 'horizon:admin:networks:detail'
+ failure_url = 'horizon:admin:networks:detail'
def get_success_url(self):
return reverse(self.success_url,
@@ -44,7 +48,7 @@ class CreateView(forms.ModalFormView):
self._object = api.quantum.network_get(self.request,
network_id)
except:
- redirect = reverse("horizon:admin:networks:detail",
+ redirect = reverse(self.failure_url,
args=(self.kwargs['network_id'],))
msg = _("Unable to retrieve network.")
exceptions.handle(self.request, msg, redirect=redirect)
@@ -61,39 +65,8 @@ class CreateView(forms.ModalFormView):
"network_name": network.name}
-class UpdateView(forms.ModalFormView):
+class UpdateView(project_views.UpdateView):
form_class = UpdatePort
template_name = 'admin/networks/ports/update.html'
context_object_name = 'port'
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']}
diff --git a/openstack_dashboard/dashboards/admin/networks/subnets/forms.py b/openstack_dashboard/dashboards/admin/networks/subnets/forms.py
deleted file mode 100644
index 2a4ab362d..000000000
--- a/openstack_dashboard/dashboards/admin/networks/subnets/forms.py
+++ /dev/null
@@ -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'
diff --git a/openstack_dashboard/dashboards/admin/networks/subnets/views.py b/openstack_dashboard/dashboards/admin/networks/subnets/views.py
index 86837cfac..5e54022ca 100644
--- a/openstack_dashboard/dashboards/admin/networks/subnets/views.py
+++ b/openstack_dashboard/dashboards/admin/networks/subnets/views.py
@@ -23,81 +23,20 @@ from horizon import exceptions
from horizon import forms
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__)
-class CreateView(forms.ModalFormView):
- form_class = CreateSubnet
+class CreateView(project_views.CreateView):
+ workflow_class = CreateSubnet
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):
- form_class = UpdateSubnet
+class UpdateView(project_views.UpdateView):
+ workflow_class = UpdateSubnet
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']}
diff --git a/openstack_dashboard/dashboards/admin/networks/subnets/workflows.py b/openstack_dashboard/dashboards/admin/networks/subnets/workflows.py
new file mode 100644
index 000000000..0b479ca37
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/networks/subnets/workflows.py
@@ -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"
diff --git a/openstack_dashboard/dashboards/admin/networks/templates/networks/subnets/_create.html b/openstack_dashboard/dashboards/admin/networks/templates/networks/subnets/_create.html
deleted file mode 100644
index c7367b4c8..000000000
--- a/openstack_dashboard/dashboards/admin/networks/templates/networks/subnets/_create.html
+++ /dev/null
@@ -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 %}
-
-
- {% include "horizon/common/_form_fields.html" %}
-
-
-
-
{% trans "Description" %}:
-
{% 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." %}
-
-{% endblock %}
-
-{% block modal-footer %}
-
- {% trans "Cancel" %}
-{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/networks/templates/networks/subnets/_update.html b/openstack_dashboard/dashboards/admin/networks/templates/networks/subnets/_update.html
deleted file mode 100644
index 45a125ea6..000000000
--- a/openstack_dashboard/dashboards/admin/networks/templates/networks/subnets/_update.html
+++ /dev/null
@@ -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 %}
-
-
- {% trans "ID" %}
- {{ subnet_id }}
- {% trans "Network Address" %}
- {{ cidr }}
- {% trans "IP version" %}
- {{ ip_version }}
-
-
-
- {% include "horizon/common/_form_fields.html" %}
-
-
-
-
{% trans "Description:" %}
-
{% trans "You may update the editable properties of your subnet here." %}
-
-{% endblock %}
-
-{% block modal-footer %}
-
- {% trans "Cancel" %}
-{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/networks/templates/networks/subnets/create.html b/openstack_dashboard/dashboards/admin/networks/templates/networks/subnets/create.html
index 84624cc13..e026151c1 100644
--- a/openstack_dashboard/dashboards/admin/networks/templates/networks/subnets/create.html
+++ b/openstack_dashboard/dashboards/admin/networks/templates/networks/subnets/create.html
@@ -7,5 +7,5 @@
{% endblock page_header %}
{% block main %}
- {% include "admin/networks/subnets/_create.html" %}
+ {% include "horizon/common/_workflow.html" %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/networks/templates/networks/subnets/update.html b/openstack_dashboard/dashboards/admin/networks/templates/networks/subnets/update.html
index b152cdf4e..6d7c62311 100644
--- a/openstack_dashboard/dashboards/admin/networks/templates/networks/subnets/update.html
+++ b/openstack_dashboard/dashboards/admin/networks/templates/networks/subnets/update.html
@@ -7,5 +7,5 @@
{% endblock page_header %}
{% block main %}
- {% include 'admin/networks/subnets/_update.html' %}
+ {% include "horizon/common/_workflow.html" %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/networks/tests.py b/openstack_dashboard/dashboards/admin/networks/tests.py
index 4ea208cae..1c4e45d13 100644
--- a/openstack_dashboard/dashboards/admin/networks/tests.py
+++ b/openstack_dashboard/dashboards/admin/networks/tests.py
@@ -21,6 +21,8 @@ from mox import IsA
from openstack_dashboard import api
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')
@@ -166,13 +168,19 @@ class NetworkTests(test.BaseAdminViewTests):
network = self.networks.first()
api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\
.AndReturn(tenants)
- api.quantum.network_create(IsA(http.HttpRequest), name=network.name,
- tenant_id=tenant_id, shared=True)\
+ params = {'name': network.name,
+ '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)
self.mox.ReplayAll()
form_data = {'tenant_id': tenant_id,
'name': network.name,
+ 'admin_state': network.admin_state_up,
+ 'external': True,
'shared': True}
url = reverse('horizon:admin:networks:create')
res = self.client.post(url, form_data)
@@ -188,13 +196,19 @@ class NetworkTests(test.BaseAdminViewTests):
network = self.networks.first()
api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\
.AndReturn(tenants)
- api.quantum.network_create(IsA(http.HttpRequest), name=network.name,
- tenant_id=tenant_id, shared=False)\
+ params = {'name': network.name,
+ '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)
self.mox.ReplayAll()
form_data = {'tenant_id': tenant_id,
'name': network.name,
+ 'admin_state': network.admin_state_up,
+ 'external': True,
'shared': False}
url = reverse('horizon:admin:networks:create')
res = self.client.post(url, form_data)
@@ -233,19 +247,25 @@ class NetworkTests(test.BaseAdminViewTests):
'network_get',)})
def test_network_update_post(self):
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,
- name=network.name, shared=True)\
+ **params)\
.AndReturn(network)
api.quantum.network_get(IsA(http.HttpRequest), network.id)\
.AndReturn(network)
self.mox.ReplayAll()
- formData = {'network_id': network.id,
- 'name': network.name,
- 'tenant_id': network.tenant_id,
- 'shared': True}
+ form_data = {'network_id': network.id,
+ 'name': network.name,
+ 'tenant_id': network.tenant_id,
+ 'admin_state': network.admin_state_up,
+ 'shared': True,
+ 'external': True}
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)
@@ -253,8 +273,12 @@ class NetworkTests(test.BaseAdminViewTests):
'network_get',)})
def test_network_update_post_exception(self):
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,
- name=network.name, shared=False)\
+ **params)\
.AndRaise(self.exceptions.quantum)
api.quantum.network_get(IsA(http.HttpRequest), network.id)\
.AndReturn(network)
@@ -263,7 +287,9 @@ class NetworkTests(test.BaseAdminViewTests):
form_data = {'network_id': network.id,
'name': network.name,
'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])
res = self.client.post(url, form_data)
@@ -308,6 +334,9 @@ class NetworkTests(test.BaseAdminViewTests):
self.assertRedirectsNoFollow(res, INDEX_URL)
+
+class NetworkSubnetTests(test.BaseAdminViewTests):
+
@test.create_stubs({api.quantum: ('subnet_get',)})
def test_subnet_detail(self):
subnet = self.subnets.first()
@@ -367,21 +396,17 @@ class NetworkTests(test.BaseAdminViewTests):
.AndReturn(self.networks.first())
api.quantum.subnet_create(IsA(http.HttpRequest),
network_id=network.id,
- network_name=network.name,
name=subnet.name,
cidr=subnet.cidr,
ip_version=subnet.ip_version,
gateway_ip=subnet.gateway_ip,
+ enable_dhcp=subnet.enable_dhcp,
+ allocation_pools=subnet.allocation_pools,
tenant_id=subnet.tenant_id)\
.AndReturn(subnet)
self.mox.ReplayAll()
- form_data = {'network_id': subnet.network_id,
- 'network_name': network.name,
- 'name': subnet.name,
- 'cidr': subnet.cidr,
- 'ip_version': subnet.ip_version,
- 'gateway_ip': subnet.gateway_ip}
+ form_data = form_data_subnet(subnet)
url = reverse('horizon:admin:networks:addsubnet',
args=[subnet.network_id])
res = self.client.post(url, form_data)
@@ -401,12 +426,7 @@ class NetworkTests(test.BaseAdminViewTests):
.AndRaise(self.exceptions.quantum)
self.mox.ReplayAll()
- form_data = {'network_id': subnet.network_id,
- 'network_name': network.name,
- 'name': subnet.name,
- 'cidr': subnet.cidr,
- 'ip_version': subnet.ip_version,
- 'gateway_ip': subnet.gateway_ip}
+ form_data = form_data_subnet(subnet, allocation_pools=[])
url = reverse('horizon:admin:networks:addsubnet',
args=[subnet.network_id])
res = self.client.post(url, form_data)
@@ -430,21 +450,16 @@ class NetworkTests(test.BaseAdminViewTests):
.AndReturn(self.networks.first())
api.quantum.subnet_create(IsA(http.HttpRequest),
network_id=network.id,
- network_name=network.name,
name=subnet.name,
cidr=subnet.cidr,
ip_version=subnet.ip_version,
gateway_ip=subnet.gateway_ip,
+ enable_dhcp=subnet.enable_dhcp,
tenant_id=subnet.tenant_id)\
.AndRaise(self.exceptions.quantum)
self.mox.ReplayAll()
- form_data = {'network_id': subnet.network_id,
- 'network_name': network.name,
- 'name': subnet.name,
- 'cidr': subnet.cidr,
- 'ip_version': subnet.ip_version,
- 'gateway_ip': subnet.gateway_ip}
+ form_data = form_data_subnet(subnet, allocation_pools=[])
url = reverse('horizon:admin:networks:addsubnet',
args=[subnet.network_id])
res = self.client.post(url, form_data)
@@ -464,12 +479,7 @@ class NetworkTests(test.BaseAdminViewTests):
# dummy IPv6 address
cidr = '2001:0DB8:0:CD30:123:4567:89AB:CDEF/60'
- form_data = {'network_id': subnet.network_id,
- 'network_name': network.name,
- 'name': subnet.name,
- 'cidr': cidr,
- 'ip_version': subnet.ip_version,
- 'gateway_ip': subnet.gateway_ip}
+ form_data = form_data_subnet(subnet, cidr=cidr, allocation_pools=[])
url = reverse('horizon:admin:networks:addsubnet',
args=[subnet.network_id])
res = self.client.post(url, form_data)
@@ -488,12 +498,8 @@ class NetworkTests(test.BaseAdminViewTests):
# dummy IPv6 address
gateway_ip = '2001:0DB8:0:CD30:123:4567:89AB:CDEF'
- form_data = {'network_id': subnet.network_id,
- 'network_name': network.name,
- 'name': subnet.name,
- 'cidr': subnet.cidr,
- 'ip_version': subnet.ip_version,
- 'gateway_ip': gateway_ip}
+ form_data = form_data_subnet(subnet, gateway_ip=gateway_ip,
+ allocation_pools=[])
url = reverse('horizon:admin:networks:addsubnet',
args=[subnet.network_id])
res = self.client.post(url, form_data)
@@ -508,20 +514,17 @@ class NetworkTests(test.BaseAdminViewTests):
.AndReturn(subnet)
api.quantum.subnet_modify(IsA(http.HttpRequest), subnet.id,
name=subnet.name,
- gateway_ip=subnet.gateway_ip)\
+ gateway_ip=subnet.gateway_ip,
+ enable_dhcp=subnet.enable_dhcp,
+ dns_nameservers=[],
+ host_routes=[])\
.AndReturn(subnet)
self.mox.ReplayAll()
- formData = {'network_id': subnet.network_id,
- '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}
+ form_data = form_data_subnet(subnet, allocation_pools=[])
url = reverse('horizon:admin:networks:editsubnet',
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',
args=[subnet.network_id])
@@ -537,16 +540,11 @@ class NetworkTests(test.BaseAdminViewTests):
# dummy IPv6 address
gateway_ip = '2001:0DB8:0:CD30:123:4567:89AB:CDEF'
- formData = {'network_id': subnet.network_id,
- 'tenant_id': subnet.tenant_id,
- 'subnet_id': subnet.id,
- 'name': subnet.name,
- 'cidr': subnet.cidr,
- 'ip_version': subnet.ip_version,
- 'gateway_ip': gateway_ip}
+ form_data = form_data_subnet(subnet, gateway_ip=gateway_ip,
+ allocation_pools=[])
url = reverse('horizon:admin:networks:editsubnet',
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.')
@@ -563,10 +561,10 @@ class NetworkTests(test.BaseAdminViewTests):
.AndReturn([self.ports.first()])
self.mox.ReplayAll()
- formData = {'action': 'subnets__delete__%s' % subnet.id}
+ form_data = {'action': 'subnets__delete__%s' % subnet.id}
url = reverse('horizon:admin:networks:detail',
args=[network_id])
- res = self.client.post(url, formData)
+ res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, url)
@@ -584,13 +582,16 @@ class NetworkTests(test.BaseAdminViewTests):
.AndReturn([self.ports.first()])
self.mox.ReplayAll()
- formData = {'action': 'subnets__delete__%s' % subnet.id}
+ form_data = {'action': 'subnets__delete__%s' % subnet.id}
url = reverse('horizon:admin:networks:detail',
args=[network_id])
- res = self.client.post(url, formData)
+ res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, url)
+
+class NetworkPortTests(test.BaseAdminViewTests):
+
@test.create_stubs({api.quantum: ('port_get',)})
def test_port_detail(self):
port = self.ports.first()
@@ -651,14 +652,18 @@ class NetworkTests(test.BaseAdminViewTests):
network_id=network.id,
network_name=network.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)
self.mox.ReplayAll()
form_data = {'network_id': port.network_id,
'network_name': network.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',
args=[port.network_id])
res = self.client.post(url, form_data)
@@ -684,14 +689,18 @@ class NetworkTests(test.BaseAdminViewTests):
network_id=network.id,
network_name=network.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)
self.mox.ReplayAll()
form_data = {'network_id': port.network_id,
'network_name': network.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',
args=[port.network_id])
res = self.client.post(url, form_data)
@@ -722,18 +731,22 @@ class NetworkTests(test.BaseAdminViewTests):
api.quantum.port_get(IsA(http.HttpRequest), port.id)\
.AndReturn(port)
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)
self.mox.ReplayAll()
- formData = {'tenant_id': port.tenant_id,
- 'network_id': port.network_id,
- 'port_id': port.id,
- 'name': port.name,
- 'device_id': port.device_id}
+ form_data = {'network_id': port.network_id,
+ 'port_id': port.id,
+ 'name': port.name,
+ 'admin_state': port.admin_state_up,
+ 'device_id': port.device_id,
+ 'device_owner': port.device_owner}
url = reverse('horizon:admin:networks:editport',
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',
args=[port.network_id])
@@ -746,18 +759,22 @@ class NetworkTests(test.BaseAdminViewTests):
api.quantum.port_get(IsA(http.HttpRequest), port.id)\
.AndReturn(port)
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)
self.mox.ReplayAll()
- formData = {'tenant_id': port.tenant_id,
- 'network_id': port.network_id,
- 'port_id': port.id,
- 'name': port.name,
- 'device_id': port.device_id}
+ form_data = {'network_id': port.network_id,
+ 'port_id': port.id,
+ 'name': port.name,
+ 'admin_state': port.admin_state_up,
+ 'device_id': port.device_id,
+ 'device_owner': port.device_owner}
url = reverse('horizon:admin:networks:editport',
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',
args=[port.network_id])
@@ -776,10 +793,10 @@ class NetworkTests(test.BaseAdminViewTests):
.AndReturn([self.ports.first()])
self.mox.ReplayAll()
- formData = {'action': 'ports__delete__%s' % port.id}
+ form_data = {'action': 'ports__delete__%s' % port.id}
url = reverse('horizon:admin:networks:detail',
args=[network_id])
- res = self.client.post(url, formData)
+ res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, url)
@@ -797,9 +814,9 @@ class NetworkTests(test.BaseAdminViewTests):
.AndReturn([self.ports.first()])
self.mox.ReplayAll()
- formData = {'action': 'ports__delete__%s' % port.id}
+ form_data = {'action': 'ports__delete__%s' % port.id}
url = reverse('horizon:admin:networks:detail',
args=[network_id])
- res = self.client.post(url, formData)
+ res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, url)
diff --git a/openstack_dashboard/dashboards/admin/networks/views.py b/openstack_dashboard/dashboards/admin/networks/views.py
index 2c38adb8e..32f08b14c 100644
--- a/openstack_dashboard/dashboards/admin/networks/views.py
+++ b/openstack_dashboard/dashboards/admin/networks/views.py
@@ -137,4 +137,6 @@ class UpdateView(user_views.UpdateView):
return {'network_id': network['id'],
'tenant_id': network['tenant_id'],
'name': network['name'],
- 'shared': network['shared']}
+ 'admin_state': network['admin_state_up'],
+ 'shared': network['shared'],
+ 'external': network['router__external']}
diff --git a/openstack_dashboard/dashboards/project/networks/forms.py b/openstack_dashboard/dashboards/project/networks/forms.py
index 8ba73a6ec..edc9e69e1 100644
--- a/openstack_dashboard/dashboards/project/networks/forms.py
+++ b/openstack_dashboard/dashboards/project/networks/forms.py
@@ -39,12 +39,15 @@ class UpdateNetwork(forms.SelfHandlingForm):
network_id = forms.CharField(label=_("ID"),
widget=forms.TextInput(
attrs={'readonly': 'readonly'}))
+ admin_state = forms.BooleanField(label=_("Admin State"), required=False)
failure_url = 'horizon:project:networks:index'
def handle(self, request, data):
try:
+ params = {'admin_state_up': data['admin_state'],
+ 'name': data['name']}
network = api.quantum.network_modify(request, data['network_id'],
- name=data['name'])
+ **params)
msg = _('Network %s was successfully updated.') % data['name']
LOG.debug(msg)
messages.success(request, msg)
diff --git a/openstack_dashboard/dashboards/project/networks/ports/forms.py b/openstack_dashboard/dashboards/project/networks/ports/forms.py
new file mode 100644
index 000000000..4e25404f0
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/networks/ports/forms.py
@@ -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)
diff --git a/openstack_dashboard/dashboards/project/networks/ports/tables.py b/openstack_dashboard/dashboards/project/networks/ports/tables.py
index 1cb6243b7..0397bbd40 100644
--- a/openstack_dashboard/dashboards/project/networks/ports/tables.py
+++ b/openstack_dashboard/dashboards/project/networks/ports/tables.py
@@ -16,6 +16,7 @@
import logging
+from django.core.urlresolvers import reverse
from django import template
from django.utils.translation import ugettext_lazy as _
@@ -32,7 +33,23 @@ def get_fixed_ips(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):
@@ -40,7 +57,7 @@ class PortsTable(tables.DataTable):
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"))
+ attached = tables.Column(get_attached, verbose_name=_("Attached Device"))
status = tables.Column("status", verbose_name=_("Status"))
admin_state = tables.Column("admin_state",
verbose_name=_("Admin State"))
@@ -51,3 +68,4 @@ class PortsTable(tables.DataTable):
class Meta:
name = "ports"
verbose_name = _("Ports")
+ row_actions = (UpdatePort,)
diff --git a/openstack_dashboard/dashboards/project/networks/ports/views.py b/openstack_dashboard/dashboards/project/networks/ports/views.py
index 14f361440..581b8aebd 100644
--- a/openstack_dashboard/dashboards/project/networks/ports/views.py
+++ b/openstack_dashboard/dashboards/project/networks/ports/views.py
@@ -14,11 +14,59 @@
# 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 forms
from horizon import tabs
+from openstack_dashboard import api
+
+from .forms import UpdatePort
from .tabs import PortDetailTabs
class DetailView(tabs.TabView):
tab_group_class = PortDetailTabs
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']}
diff --git a/openstack_dashboard/dashboards/project/networks/subnets/forms.py b/openstack_dashboard/dashboards/project/networks/subnets/forms.py
deleted file mode 100644
index c03febb59..000000000
--- a/openstack_dashboard/dashboards/project/networks/subnets/forms.py
+++ /dev/null
@@ -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)
diff --git a/openstack_dashboard/dashboards/project/networks/subnets/views.py b/openstack_dashboard/dashboards/project/networks/subnets/views.py
index 52d308023..580f5f59e 100644
--- a/openstack_dashboard/dashboards/project/networks/subnets/views.py
+++ b/openstack_dashboard/dashboards/project/networks/subnets/views.py
@@ -22,26 +22,21 @@ import logging
from django.core.urlresolvers import reverse_lazy, reverse
from django.utils.translation import ugettext_lazy as _
-from horizon import forms
from horizon import exceptions
from horizon import tabs
+from horizon import workflows
from openstack_dashboard import api
-from .forms import CreateSubnet, UpdateSubnet
from .tabs import SubnetDetailTabs
+from .workflows import CreateSubnet, UpdateSubnet
LOG = logging.getLogger(__name__)
-class CreateView(forms.ModalFormView):
- form_class = CreateSubnet
+class CreateView(workflows.WorkflowView):
+ workflow_class = CreateSubnet
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):
if not hasattr(self, "_object"):
@@ -49,32 +44,22 @@ class CreateView(forms.ModalFormView):
network_id = self.kwargs["network_id"]
self._object = api.quantum.network_get(self.request,
network_id)
+ self._object.set_id_as_name_if_empty()
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):
- form_class = UpdateSubnet
+class UpdateView(workflows.WorkflowView):
+ workflow_class = UpdateSubnet
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):
if not hasattr(self, "_object"):
@@ -87,23 +72,30 @@ class UpdateView(forms.ModalFormView):
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'] = subnet.ipver_str
- return context
-
def get_initial(self):
+ initial = super(UpdateView, self).get_initial()
+
subnet = self._get_object()
- return {'network_id': self.kwargs['network_id'],
- 'subnet_id': subnet['id'],
- 'cidr': subnet['cidr'],
- 'ip_version': subnet['ip_version'],
- 'name': subnet['name'],
- 'gateway_ip': subnet['gateway_ip']}
+
+ initial['network_id'] = self.kwargs['network_id']
+ initial['subnet_id'] = subnet['id']
+ initial['subnet_name'] = subnet['name']
+
+ 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):
diff --git a/openstack_dashboard/dashboards/project/networks/subnets/workflows.py b/openstack_dashboard/dashboards/project/networks/subnets/workflows.py
new file mode 100644
index 000000000..469c609d2
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/networks/subnets/workflows.py
@@ -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
diff --git a/openstack_dashboard/dashboards/project/networks/templates/networks/_detail_overview.html b/openstack_dashboard/dashboards/project/networks/templates/networks/_detail_overview.html
index b64341fb5..b8c87a7a2 100644
--- a/openstack_dashboard/dashboards/project/networks/templates/networks/_detail_overview.html
+++ b/openstack_dashboard/dashboards/project/networks/templates/networks/_detail_overview.html
@@ -16,5 +16,13 @@
{{ network.admin_state|default:"Unknown" }}
{% trans "Shared" %}
{{ network.shared|yesno|capfirst }}
+ {% trans "External Network" %}
+ {{ network.router__external|yesno|capfirst }}
+ {% if network.provider__network_type %}
+ {% trans "Provider Network" %}
+ {% trans "Network Type" %}: {{ network.provider__network_type|default:"Unknown" }}
+ {% trans "Physical Network" %}: {{ network.provider__physical_network|default:"-" }}
+ {% trans "Segmentation ID" %}: {{ network.provider__segmentation_id|default:"-" }}
+ {% endif %}
diff --git a/openstack_dashboard/dashboards/project/networks/templates/networks/ports/_detail_overview.html b/openstack_dashboard/dashboards/project/networks/templates/networks/ports/_detail_overview.html
index 401a5db69..166fcd12d 100644
--- a/openstack_dashboard/dashboards/project/networks/templates/networks/ports/_detail_overview.html
+++ b/openstack_dashboard/dashboards/project/networks/templates/networks/ports/_detail_overview.html
@@ -10,8 +10,9 @@
{{ port.name|default:"None" }}
{% trans "ID" %}
{{ port.id|default:"None" }}
+ {% url horizon:project:networks:detail port.network_id as network_url %}
{% trans "Network ID" %}
- {{ port.network_id|default:"None" }}
+ {{ port.network_id|default:"None" }}
{% trans "Project ID" %}
{{ port.tenant_id|default:"-" }}
{% trans "Fixed IP" %}
@@ -31,9 +32,10 @@
{{ port.status|default:"None" }}
{% trans "Admin State" %}
{{ port.admin_state|default:"None" }}
- {% trans "Device ID" %}
- {% if port.device_id|length > 1 %}
- {{ port.device_id }}
+ {% trans "Attached Device" %}
+ {% if port.device_id|length > 1 or port.device_owner %}
+ {% trans "Device Owner" %} : {{ port.device_owner|default:"None" }}
+ {% trans "Device ID" %} : {{ port.device_id|default:"-" }}
{% else %}
No attached device
{% endif %}
diff --git a/openstack_dashboard/dashboards/project/networks/templates/networks/subnets/_update.html b/openstack_dashboard/dashboards/project/networks/templates/networks/ports/_update.html
similarity index 61%
rename from openstack_dashboard/dashboards/project/networks/templates/networks/subnets/_update.html
rename to openstack_dashboard/dashboards/project/networks/templates/networks/ports/_update.html
index f74b5e280..e490f5a19 100644
--- a/openstack_dashboard/dashboards/project/networks/templates/networks/subnets/_update.html
+++ b/openstack_dashboard/dashboards/project/networks/templates/networks/ports/_update.html
@@ -1,20 +1,16 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
-{% block form_id %}update_subnet_form{% endblock %}
-{% block form_action %}{% url horizon:project:networks:editsubnet network_id subnet_id %}{% endblock %}
+{% block form_id %}update_port_form{% 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 %}
{% trans "ID" %}
- {{ subnet_id }}
- {% trans "Network Address" %}
- {{ cidr }}
- {% trans "IP version" %}
- {{ ip_version }}
+ {{ port_id }}
@@ -23,7 +19,7 @@
{% trans "Description:" %}
-
{% trans "You may update the editable properties of your subnet here." %}
+
{% trans "You may update the editable properties of your port here." %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/networks/templates/networks/ports/update.html b/openstack_dashboard/dashboards/project/networks/templates/networks/ports/update.html
new file mode 100644
index 000000000..f6b932294
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/networks/templates/networks/ports/update.html
@@ -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 %}
diff --git a/openstack_dashboard/dashboards/project/networks/templates/networks/subnets/_create.html b/openstack_dashboard/dashboards/project/networks/templates/networks/subnets/_create.html
deleted file mode 100644
index cd4ebbc1c..000000000
--- a/openstack_dashboard/dashboards/project/networks/templates/networks/subnets/_create.html
+++ /dev/null
@@ -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 %}
-
-
- {% include "horizon/common/_form_fields.html" %}
-
-
-
-
{% trans "Description" %}:
-
{% 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." %}
-
-{% endblock %}
-
-{% block modal-footer %}
-
- {% trans "Cancel" %}
-{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/networks/templates/networks/subnets/_detail_overview.html b/openstack_dashboard/dashboards/project/networks/templates/networks/subnets/_detail_overview.html
index 4c09fde09..5aa9c1ad6 100644
--- a/openstack_dashboard/dashboards/project/networks/templates/networks/subnets/_detail_overview.html
+++ b/openstack_dashboard/dashboards/project/networks/templates/networks/subnets/_detail_overview.html
@@ -10,14 +10,13 @@
{{ subnet.name|default:"None" }}
{% trans "ID" %}
{{ subnet.id|default:"None" }}
+ {% url horizon:project:networks:detail subnet.network_id as network_url %}
{% trans "Network ID" %}
- {{ subnet.network_id|default:"None" }}
- {% trans "CIDR" %}
- {{ subnet.cidr|default:"None" }}
+ {{ subnet.network_id|default:"None" }}
{% trans "IP version" %}
{{ subnet.ipver_str|default:"-" }}
- {% trans "Gateway IP" %}
- {{ subnet.gateway_ip|default:"-" }}
+ {% trans "CIDR" %}
+ {{ subnet.cidr|default:"None" }}
{% trans "IP allocation pool" %}
{% for pool in subnet.allocation_pools %}
@@ -25,5 +24,26 @@
{% trans " - End" %} {{ pool.end }}
{% endfor %}
+ {% trans "DHCP Enable" %}
+ {{ subnet.enable_dhcp|yesno|capfirst }}
+ {% trans "Gateway IP" %}
+ {{ subnet.gateway_ip|default:"-" }}
+ {% trans "Additional routes" %}
+
+ {% for route in subnet.host_routes %}
+ {% trans "Destination" %} {{ route.destination }}
+ {% trans " : Next hop" %} {{ route.nexthop }}
+ {% empty %}
+ {% trans "None" %}
+ {% endfor %}
+
+ {% trans "DNS name server" %}
+
+ {% for dns in subnet.dns_nameservers %}
+ {{ dns }}
+ {% empty %}
+ {% trans "None" %}
+ {% endfor %}
+
diff --git a/openstack_dashboard/dashboards/project/networks/templates/networks/subnets/create.html b/openstack_dashboard/dashboards/project/networks/templates/networks/subnets/create.html
index 7a614b1b8..e026151c1 100644
--- a/openstack_dashboard/dashboards/project/networks/templates/networks/subnets/create.html
+++ b/openstack_dashboard/dashboards/project/networks/templates/networks/subnets/create.html
@@ -7,5 +7,5 @@
{% endblock page_header %}
{% block main %}
- {% include "project/networks/subnets/_create.html" %}
+ {% include "horizon/common/_workflow.html" %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/networks/templates/networks/subnets/update.html b/openstack_dashboard/dashboards/project/networks/templates/networks/subnets/update.html
index 1d74569d6..6d7c62311 100644
--- a/openstack_dashboard/dashboards/project/networks/templates/networks/subnets/update.html
+++ b/openstack_dashboard/dashboards/project/networks/templates/networks/subnets/update.html
@@ -7,5 +7,5 @@
{% endblock page_header %}
{% block main %}
- {% include 'project/networks/subnets/_update.html' %}
+ {% include "horizon/common/_workflow.html" %}
{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/networks/tests.py b/openstack_dashboard/dashboards/project/networks/tests.py
index a9e246929..19038e776 100644
--- a/openstack_dashboard/dashboards/project/networks/tests.py
+++ b/openstack_dashboard/dashboards/project/networks/tests.py
@@ -1,5 +1,5 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
+#
# Copyright 2012 NEC Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -19,6 +19,7 @@ from django.core.urlresolvers import reverse
from django.utils.html import escape
from mox import IsA
+import netaddr
from openstack_dashboard import api
from openstack_dashboard.test import helpers as test
@@ -28,7 +29,70 @@ from .workflows import CreateNetwork
INDEX_URL = reverse('horizon:project:networks:index')
+def form_data_subnet(subnet,
+ name=None, cidr=None, ip_version=None,
+ gateway_ip='', enable_dhcp=None,
+ allocation_pools=None,
+ dns_nameservers=None,
+ host_routes=None):
+ def get_value(value, default):
+ return default if value is None else value
+
+ data = {}
+ data['subnet_name'] = get_value(name, subnet.name)
+ data['cidr'] = get_value(cidr, subnet.cidr)
+ data['ip_version'] = get_value(ip_version, subnet.ip_version)
+
+ gateway_ip = subnet.gateway_ip if gateway_ip == '' else gateway_ip
+ data['gateway_ip'] = gateway_ip or ''
+ data['no_gateway'] = (gateway_ip is None)
+
+ data['enable_dhcp'] = get_value(enable_dhcp, subnet.enable_dhcp)
+
+ pools = get_value(allocation_pools, subnet.allocation_pools)
+ data['allocation_pools'] = _str_allocation_pools(pools)
+ nameservers = get_value(dns_nameservers, subnet.dns_nameservers)
+ data['dns_nameservers'] = _str_dns_nameservers(nameservers)
+ routes = get_value(host_routes, subnet.host_routes)
+ data['host_routes'] = _str_host_routes(routes)
+
+ return data
+
+
+def form_data_no_subnet():
+ return {'subnet_name': '',
+ 'cidr': '',
+ 'ip_version': 4,
+ 'gateway_ip': '',
+ 'no_gateway': False,
+ 'enable_dhcp': True,
+ 'allocation_pools': '',
+ 'dns_nameservers': '',
+ 'host_routes': ''}
+
+
+def _str_allocation_pools(allocation_pools):
+ if isinstance(allocation_pools, str):
+ return allocation_pools
+ return '\n'.join(['%s,%s' % (pool['start'], pool['end'])
+ for pool in allocation_pools])
+
+
+def _str_dns_nameservers(dns_nameservers):
+ if isinstance(dns_nameservers, str):
+ return dns_nameservers
+ return '\n'.join(dns_nameservers)
+
+
+def _str_host_routes(host_routes):
+ if isinstance(host_routes, str):
+ return host_routes
+ return '\n'.join(['%s,%s' % (route['destination'], route['nexthop'])
+ for route in host_routes])
+
+
class NetworkTests(test.TestCase):
+
@test.create_stubs({api.quantum: ('network_list',)})
def test_index(self):
api.quantum.network_list(
@@ -164,22 +228,23 @@ class NetworkTests(test.TestCase):
self.assertTemplateUsed(res, 'project/networks/create.html')
self.assertEqual(workflow.name, CreateNetwork.name)
expected_objs = ['',
- '']
+ '',
+ '']
self.assertQuerysetEqual(workflow.steps, expected_objs)
@test.create_stubs({api.quantum: ('network_create',)})
def test_network_create_post(self):
network = self.networks.first()
- api.quantum.network_create(IsA(http.HttpRequest), name=network.name)\
+ api.quantum.network_create(IsA(http.HttpRequest), name=network.name,
+ admin_state_up=network.admin_state_up)\
.AndReturn(network)
self.mox.ReplayAll()
form_data = {'net_name': network.name,
- 'with_subnet': False,
- 'subnet_name': '',
- 'cidr': '',
- 'ip_version': 4,
- 'gateway_ip': ''}
+ 'admin_state': network.admin_state_up,
+ # subnet
+ 'with_subnet': False}
+ form_data.update(form_data_no_subnet())
url = reverse('horizon:project:networks:create')
res = self.client.post(url, form_data)
@@ -191,23 +256,23 @@ class NetworkTests(test.TestCase):
def test_network_create_post_with_subnet(self):
network = self.networks.first()
subnet = self.subnets.first()
- api.quantum.network_create(IsA(http.HttpRequest), name=network.name)\
+ api.quantum.network_create(IsA(http.HttpRequest), name=network.name,
+ admin_state_up=network.admin_state_up)\
.AndReturn(network)
api.quantum.subnet_create(IsA(http.HttpRequest),
network_id=network.id,
name=subnet.name,
cidr=subnet.cidr,
ip_version=subnet.ip_version,
- gateway_ip=subnet.gateway_ip)\
+ gateway_ip=subnet.gateway_ip,
+ enable_dhcp=subnet.enable_dhcp)\
.AndReturn(subnet)
self.mox.ReplayAll()
form_data = {'net_name': network.name,
- 'with_subnet': True,
- 'subnet_name': subnet.name,
- 'cidr': subnet.cidr,
- 'ip_version': subnet.ip_version,
- 'gateway_ip': subnet.gateway_ip}
+ 'admin_state': network.admin_state_up,
+ 'with_subnet': True}
+ form_data.update(form_data_subnet(subnet, allocation_pools=[]))
url = reverse('horizon:project:networks:create')
res = self.client.post(url, form_data)
@@ -217,16 +282,16 @@ class NetworkTests(test.TestCase):
@test.create_stubs({api.quantum: ('network_create',)})
def test_network_create_post_network_exception(self):
network = self.networks.first()
- api.quantum.network_create(IsA(http.HttpRequest), name=network.name)\
+ api.quantum.network_create(IsA(http.HttpRequest), name=network.name,
+ admin_state_up=network.admin_state_up)\
.AndRaise(self.exceptions.quantum)
self.mox.ReplayAll()
form_data = {'net_name': network.name,
- 'with_subnet': False,
- 'subnet_name': '',
- 'cidr': '',
- 'ip_version': 4,
- 'gateway_ip': ''}
+ 'admin_state': network.admin_state_up,
+ # subnet
+ 'with_subnet': False}
+ form_data.update(form_data_no_subnet())
url = reverse('horizon:project:networks:create')
res = self.client.post(url, form_data)
@@ -237,16 +302,15 @@ class NetworkTests(test.TestCase):
def test_network_create_post_with_subnet_network_exception(self):
network = self.networks.first()
subnet = self.subnets.first()
- api.quantum.network_create(IsA(http.HttpRequest), name=network.name)\
+ api.quantum.network_create(IsA(http.HttpRequest), name=network.name,
+ admin_state_up=network.admin_state_up)\
.AndRaise(self.exceptions.quantum)
self.mox.ReplayAll()
form_data = {'net_name': network.name,
- 'with_subnet': True,
- 'subnet_name': subnet.name,
- 'cidr': subnet.cidr,
- 'ip_version': subnet.ip_version,
- 'gateway_ip': subnet.gateway_ip}
+ 'admin_state': network.admin_state_up,
+ 'with_subnet': True}
+ form_data.update(form_data_subnet(subnet, allocation_pools=[]))
url = reverse('horizon:project:networks:create')
res = self.client.post(url, form_data)
@@ -254,27 +318,30 @@ class NetworkTests(test.TestCase):
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api.quantum: ('network_create',
+ 'network_delete',
'subnet_create',)})
def test_network_create_post_with_subnet_subnet_exception(self):
network = self.networks.first()
subnet = self.subnets.first()
- api.quantum.network_create(IsA(http.HttpRequest), name=network.name)\
+ api.quantum.network_create(IsA(http.HttpRequest), name=network.name,
+ admin_state_up=network.admin_state_up)\
.AndReturn(network)
api.quantum.subnet_create(IsA(http.HttpRequest),
network_id=network.id,
name=subnet.name,
cidr=subnet.cidr,
ip_version=subnet.ip_version,
- gateway_ip=subnet.gateway_ip)\
+ gateway_ip=subnet.gateway_ip,
+ enable_dhcp=subnet.enable_dhcp)\
.AndRaise(self.exceptions.quantum)
+ api.quantum.network_delete(IsA(http.HttpRequest),
+ network.id)
self.mox.ReplayAll()
form_data = {'net_name': network.name,
- 'with_subnet': True,
- 'subnet_name': subnet.name,
- 'cidr': subnet.cidr,
- 'ip_version': subnet.ip_version,
- 'gateway_ip': subnet.gateway_ip}
+ 'admin_state': network.admin_state_up,
+ 'with_subnet': True}
+ form_data.update(form_data_subnet(subnet, allocation_pools=[]))
url = reverse('horizon:project:networks:create')
res = self.client.post(url, form_data)
@@ -287,11 +354,10 @@ class NetworkTests(test.TestCase):
self.mox.ReplayAll()
form_data = {'net_name': network.name,
- 'with_subnet': True,
- 'subnet_name': subnet.name,
- 'cidr': '',
- 'ip_version': subnet.ip_version,
- 'gateway_ip': subnet.gateway_ip}
+ 'admin_state': network.admin_state_up,
+ 'with_subnet': True}
+ form_data.update(form_data_subnet(subnet, cidr='',
+ allocation_pools=[]))
url = reverse('horizon:project:networks:create')
res = self.client.post(url, form_data)
@@ -306,11 +372,10 @@ class NetworkTests(test.TestCase):
# dummy IPv6 address
cidr = '2001:0DB8:0:CD30:123:4567:89AB:CDEF/60'
form_data = {'net_name': network.name,
- 'with_subnet': True,
- 'subnet_name': subnet.name,
- 'cidr': cidr,
- 'ip_version': subnet.ip_version,
- 'gateway_ip': subnet.gateway_ip}
+ 'admin_state': network.admin_state_up,
+ 'with_subnet': True}
+ form_data.update(form_data_subnet(subnet, cidr=cidr,
+ allocation_pools=[]))
url = reverse('horizon:project:networks:create')
res = self.client.post(url, form_data)
@@ -325,11 +390,10 @@ class NetworkTests(test.TestCase):
# dummy IPv6 address
gateway_ip = '2001:0DB8:0:CD30:123:4567:89AB:CDEF'
form_data = {'net_name': network.name,
- 'with_subnet': True,
- 'subnet_name': subnet.name,
- 'cidr': subnet.cidr,
- 'ip_version': subnet.ip_version,
- 'gateway_ip': gateway_ip}
+ 'admin_state': network.admin_state_up,
+ 'with_subnet': True}
+ form_data.update(form_data_subnet(subnet, gateway_ip=gateway_ip,
+ allocation_pools=[]))
url = reverse('horizon:project:networks:create')
res = self.client.post(url, form_data)
@@ -367,17 +431,19 @@ class NetworkTests(test.TestCase):
def test_network_update_post(self):
network = self.networks.first()
api.quantum.network_modify(IsA(http.HttpRequest), network.id,
- name=network.name)\
+ name=network.name,
+ admin_state_up=network.admin_state_up)\
.AndReturn(network)
api.quantum.network_get(IsA(http.HttpRequest), network.id)\
.AndReturn(network)
self.mox.ReplayAll()
- formData = {'network_id': network.id,
- 'name': network.name,
- 'tenant_id': network.tenant_id}
+ form_data = {'network_id': network.id,
+ 'name': network.name,
+ 'admin_state': network.admin_state_up,
+ 'tenant_id': network.tenant_id}
url = reverse('horizon:project:networks:update', args=[network.id])
- res = self.client.post(url, formData)
+ res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, INDEX_URL)
@@ -386,7 +452,8 @@ class NetworkTests(test.TestCase):
def test_network_update_post_exception(self):
network = self.networks.first()
api.quantum.network_modify(IsA(http.HttpRequest), network.id,
- name=network.name)\
+ name=network.name,
+ admin_state_up=network.admin_state_up)\
.AndRaise(self.exceptions.quantum)
api.quantum.network_get(IsA(http.HttpRequest), network.id)\
.AndReturn(network)
@@ -394,6 +461,7 @@ class NetworkTests(test.TestCase):
form_data = {'network_id': network.id,
'name': network.name,
+ 'admin_state': network.admin_state_up,
'tenant_id': network.tenant_id}
url = reverse('horizon:project:networks:update', args=[network.id])
res = self.client.post(url, form_data)
@@ -475,6 +543,9 @@ class NetworkTests(test.TestCase):
self.assertRedirectsNoFollow(res, INDEX_URL)
+
+class NetworkSubnetTests(test.TestCase):
+
@test.create_stubs({api.quantum: ('subnet_get',)})
def test_subnet_detail(self):
subnet = self.subnets.first()
@@ -528,20 +599,76 @@ class NetworkTests(test.TestCase):
.AndReturn(self.networks.first())
api.quantum.subnet_create(IsA(http.HttpRequest),
network_id=network.id,
- network_name=network.name,
name=subnet.name,
cidr=subnet.cidr,
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)\
.AndReturn(subnet)
self.mox.ReplayAll()
- form_data = {'network_id': subnet.network_id,
- 'network_name': network.name,
- 'name': subnet.name,
- 'cidr': subnet.cidr,
- 'ip_version': subnet.ip_version,
- 'gateway_ip': subnet.gateway_ip}
+ form_data = form_data_subnet(subnet)
+ url = reverse('horizon:project:networks:addsubnet',
+ args=[subnet.network_id])
+ res = self.client.post(url, form_data)
+
+ self.assertNoFormErrors(res)
+ redir_url = reverse('horizon:project:networks:detail',
+ args=[subnet.network_id])
+ self.assertRedirectsNoFollow(res, redir_url)
+
+ @test.create_stubs({api.quantum: ('network_get',
+ 'subnet_create',)})
+ def test_subnet_create_post_with_additional_attributes(self):
+ network = self.networks.list()[1]
+ subnet = self.subnets.list()[1]
+ api.quantum.network_get(IsA(http.HttpRequest),
+ network.id)\
+ .AndReturn(self.networks.first())
+ api.quantum.subnet_create(IsA(http.HttpRequest),
+ network_id=network.id,
+ name=subnet.name,
+ cidr=subnet.cidr,
+ ip_version=subnet.ip_version,
+ gateway_ip=subnet.gateway_ip,
+ enable_dhcp=subnet.enable_dhcp,
+ allocation_pools=subnet.allocation_pools,
+ dns_nameservers=subnet.dns_nameservers,
+ host_routes=subnet.host_routes)\
+ .AndReturn(subnet)
+ self.mox.ReplayAll()
+
+ form_data = form_data_subnet(subnet)
+ url = reverse('horizon:project:networks:addsubnet',
+ args=[subnet.network_id])
+ res = self.client.post(url, form_data)
+
+ self.assertNoFormErrors(res)
+ redir_url = reverse('horizon:project:networks:detail',
+ args=[subnet.network_id])
+ self.assertRedirectsNoFollow(res, redir_url)
+
+ @test.create_stubs({api.quantum: ('network_get',
+ 'subnet_create',)})
+ def test_subnet_create_post_with_additional_attributes(self):
+ network = self.networks.first()
+ subnet = self.subnets.first()
+ api.quantum.network_get(IsA(http.HttpRequest),
+ network.id)\
+ .AndReturn(self.networks.first())
+ api.quantum.subnet_create(IsA(http.HttpRequest),
+ network_id=network.id,
+ name=subnet.name,
+ cidr=subnet.cidr,
+ ip_version=subnet.ip_version,
+ gateway_ip=None,
+ enable_dhcp=subnet.enable_dhcp,
+ allocation_pools=subnet.allocation_pools)\
+ .AndReturn(subnet)
+ self.mox.ReplayAll()
+
+ form_data = form_data_subnet(subnet, gateway_ip=None)
url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id])
res = self.client.post(url, form_data)
@@ -561,12 +688,8 @@ class NetworkTests(test.TestCase):
.AndRaise(self.exceptions.quantum)
self.mox.ReplayAll()
- form_data = {'network_id': subnet.network_id,
- 'network_name': network.name,
- 'name': subnet.name,
- 'cidr': subnet.cidr,
- 'ip_version': subnet.ip_version,
- 'gateway_ip': subnet.gateway_ip}
+ form_data = form_data_subnet(subnet,
+ allocation_pools=[])
url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id])
res = self.client.post(url, form_data)
@@ -584,20 +707,16 @@ class NetworkTests(test.TestCase):
.AndReturn(self.networks.first())
api.quantum.subnet_create(IsA(http.HttpRequest),
network_id=network.id,
- network_name=network.name,
name=subnet.name,
cidr=subnet.cidr,
ip_version=subnet.ip_version,
- gateway_ip=subnet.gateway_ip)\
+ gateway_ip=subnet.gateway_ip,
+ enable_dhcp=subnet.enable_dhcp)\
.AndRaise(self.exceptions.quantum)
self.mox.ReplayAll()
- form_data = {'network_id': subnet.network_id,
- 'network_name': network.name,
- 'name': subnet.name,
- 'cidr': subnet.cidr,
- 'ip_version': subnet.ip_version,
- 'gateway_ip': subnet.gateway_ip}
+ form_data = form_data_subnet(subnet,
+ allocation_pools=[])
url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id])
res = self.client.post(url, form_data)
@@ -617,18 +736,16 @@ class NetworkTests(test.TestCase):
# dummy IPv6 address
cidr = '2001:0DB8:0:CD30:123:4567:89AB:CDEF/60'
- form_data = {'network_id': subnet.network_id,
- 'network_name': network.name,
- 'name': subnet.name,
- 'cidr': cidr,
- 'ip_version': subnet.ip_version,
- 'gateway_ip': subnet.gateway_ip}
+ form_data = form_data_subnet(subnet, cidr=cidr,
+ allocation_pools=[])
url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id])
res = self.client.post(url, form_data)
expected_msg = 'Network Address and IP version are inconsistent.'
- self.assertContains(res, expected_msg)
+ self.assertFormErrors(res, 1, expected_msg)
+ self.assertTemplateUsed(res,
+ 'project/networks/subnets/create.html')
@test.create_stubs({api.quantum: ('network_get',)})
def test_subnet_create_post_gw_inconsistent(self):
@@ -641,18 +758,220 @@ class NetworkTests(test.TestCase):
# dummy IPv6 address
gateway_ip = '2001:0DB8:0:CD30:123:4567:89AB:CDEF'
- form_data = {'network_id': subnet.network_id,
- 'network_name': network.name,
- 'name': subnet.name,
- 'cidr': subnet.cidr,
- 'ip_version': subnet.ip_version,
- 'gateway_ip': gateway_ip}
+ form_data = form_data_subnet(subnet, gateway_ip=gateway_ip,
+ allocation_pools=[])
url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id])
res = self.client.post(url, form_data)
self.assertContains(res, 'Gateway IP and IP version are inconsistent.')
+ @test.create_stubs({api.quantum: ('network_get',)})
+ def test_subnet_create_post_invalid_pools_start_only(self):
+ network = self.networks.first()
+ subnet = self.subnets.first()
+ api.quantum.network_get(IsA(http.HttpRequest),
+ network.id).AndReturn(network)
+ self.mox.ReplayAll()
+
+ # Start only allocation_pools
+ allocation_pools = '10.0.0.2'
+ form_data = form_data_subnet(subnet,
+ allocation_pools=allocation_pools)
+ url = reverse('horizon:project:networks:addsubnet',
+ args=[subnet.network_id])
+ res = self.client.post(url, form_data)
+
+ self.assertContains(res,
+ 'Start and end addresses must be specified '
+ '(value=%s)' % allocation_pools)
+
+ @test.create_stubs({api.quantum: ('network_get',)})
+ def test_subnet_create_post_invalid_pools_three_entries(self):
+ network = self.networks.first()
+ subnet = self.subnets.first()
+ api.quantum.network_get(IsA(http.HttpRequest),
+ network.id).AndReturn(network)
+ self.mox.ReplayAll()
+
+ # pool with three entries
+ allocation_pools = '10.0.0.2,10.0.0.3,10.0.0.4'
+ form_data = form_data_subnet(subnet,
+ allocation_pools=allocation_pools)
+ url = reverse('horizon:project:networks:addsubnet',
+ args=[subnet.network_id])
+ res = self.client.post(url, form_data)
+
+ self.assertContains(res,
+ 'Start and end addresses must be specified '
+ '(value=%s)' % allocation_pools)
+
+ @test.create_stubs({api.quantum: ('network_get',)})
+ def test_subnet_create_post_invalid_pools_invalid_address(self):
+ network = self.networks.first()
+ subnet = self.subnets.first()
+ api.quantum.network_get(IsA(http.HttpRequest),
+ network.id).AndReturn(network)
+ self.mox.ReplayAll()
+
+ # end address is not a valid IP address
+ allocation_pools = '10.0.0.2,invalid_address'
+ form_data = form_data_subnet(subnet,
+ allocation_pools=allocation_pools)
+ url = reverse('horizon:project:networks:addsubnet',
+ args=[subnet.network_id])
+ res = self.client.post(url, form_data)
+
+ self.assertContains(res,
+ 'allocation_pools: Invalid IP address '
+ '(value=%s)' % allocation_pools.split(',')[1])
+
+ @test.create_stubs({api.quantum: ('network_get',)})
+ def test_subnet_create_post_invalid_pools_ip_network(self):
+ network = self.networks.first()
+ subnet = self.subnets.first()
+ api.quantum.network_get(IsA(http.HttpRequest),
+ network.id).AndReturn(network)
+ self.mox.ReplayAll()
+
+ # start address is CIDR
+ allocation_pools = '10.0.0.2/24,10.0.0.5'
+ form_data = form_data_subnet(subnet,
+ allocation_pools=allocation_pools)
+ url = reverse('horizon:project:networks:addsubnet',
+ args=[subnet.network_id])
+ res = self.client.post(url, form_data)
+
+ self.assertContains(res,
+ 'allocation_pools: Invalid IP address '
+ '(value=%s)' % allocation_pools.split(',')[0])
+
+ @test.create_stubs({api.quantum: ('network_get',)})
+ def test_subnet_create_post_invalid_pools_start_larger_than_end(self):
+ network = self.networks.first()
+ subnet = self.subnets.first()
+ api.quantum.network_get(IsA(http.HttpRequest),
+ network.id).AndReturn(network)
+ self.mox.ReplayAll()
+
+ # start address is larger than end address
+ allocation_pools = '10.0.0.254,10.0.0.2'
+ form_data = form_data_subnet(subnet,
+ allocation_pools=allocation_pools)
+ url = reverse('horizon:project:networks:addsubnet',
+ args=[subnet.network_id])
+ res = self.client.post(url, form_data)
+
+ self.assertContains(res,
+ 'Start address is larger than end address '
+ '(value=%s)' % allocation_pools)
+
+ @test.create_stubs({api.quantum: ('network_get',)})
+ def test_subnet_create_post_invalid_nameservers(self):
+ network = self.networks.first()
+ subnet = self.subnets.first()
+ api.quantum.network_get(IsA(http.HttpRequest),
+ network.id).AndReturn(network)
+ self.mox.ReplayAll()
+
+ # invalid DNS server address
+ dns_nameservers = ['192.168.0.2', 'invalid_address']
+ form_data = form_data_subnet(subnet, dns_nameservers=dns_nameservers,
+ allocation_pools=[])
+ url = reverse('horizon:project:networks:addsubnet',
+ args=[subnet.network_id])
+ res = self.client.post(url, form_data)
+
+ self.assertContains(res,
+ 'dns_nameservers: Invalid IP address '
+ '(value=%s)' % dns_nameservers[1])
+
+ @test.create_stubs({api.quantum: ('network_get',)})
+ def test_subnet_create_post_invalid_routes_destination_only(self):
+ network = self.networks.first()
+ subnet = self.subnets.first()
+ api.quantum.network_get(IsA(http.HttpRequest),
+ network.id).AndReturn(network)
+ self.mox.ReplayAll()
+
+ # Start only host_route
+ host_routes = '192.168.0.0/24'
+ form_data = form_data_subnet(subnet,
+ allocation_pools=[],
+ host_routes=host_routes)
+ url = reverse('horizon:project:networks:addsubnet',
+ args=[subnet.network_id])
+ res = self.client.post(url, form_data)
+
+ self.assertContains(res,
+ 'Host Routes format error: '
+ 'Destination CIDR and nexthop must be specified '
+ '(value=%s)' % host_routes)
+
+ @test.create_stubs({api.quantum: ('network_get',)})
+ def test_subnet_create_post_invalid_routes_three_entries(self):
+ network = self.networks.first()
+ subnet = self.subnets.first()
+ api.quantum.network_get(IsA(http.HttpRequest),
+ network.id).AndReturn(network)
+ self.mox.ReplayAll()
+
+ # host_route with three entries
+ host_routes = 'aaaa,bbbb,cccc'
+ form_data = form_data_subnet(subnet,
+ allocation_pools=[],
+ host_routes=host_routes)
+ url = reverse('horizon:project:networks:addsubnet',
+ args=[subnet.network_id])
+ res = self.client.post(url, form_data)
+
+ self.assertContains(res,
+ 'Host Routes format error: '
+ 'Destination CIDR and nexthop must be specified '
+ '(value=%s)' % host_routes)
+
+ @test.create_stubs({api.quantum: ('network_get',)})
+ def test_subnet_create_post_invalid_routes_invalid_destination(self):
+ network = self.networks.first()
+ subnet = self.subnets.first()
+ api.quantum.network_get(IsA(http.HttpRequest),
+ network.id).AndReturn(network)
+ self.mox.ReplayAll()
+
+ # invalid destination network
+ host_routes = '172.16.0.0/64,10.0.0.253'
+ form_data = form_data_subnet(subnet,
+ host_routes=host_routes,
+ allocation_pools=[])
+ url = reverse('horizon:project:networks:addsubnet',
+ args=[subnet.network_id])
+ res = self.client.post(url, form_data)
+
+ self.assertContains(res,
+ 'host_routes: Invalid IP address '
+ '(value=%s)' % host_routes.split(',')[0])
+
+ @test.create_stubs({api.quantum: ('network_get',)})
+ def test_subnet_create_post_invalid_routes_nexthop_ip_network(self):
+ network = self.networks.first()
+ subnet = self.subnets.first()
+ api.quantum.network_get(IsA(http.HttpRequest),
+ network.id).AndReturn(network)
+ self.mox.ReplayAll()
+
+ # nexthop is not an IP address
+ host_routes = '172.16.0.0/24,10.0.0.253/24'
+ form_data = form_data_subnet(subnet,
+ host_routes=host_routes,
+ allocation_pools=[])
+ url = reverse('horizon:project:networks:addsubnet',
+ args=[subnet.network_id])
+ res = self.client.post(url, form_data)
+
+ self.assertContains(res,
+ 'host_routes: Invalid IP address '
+ '(value=%s)' % host_routes.split(',')[1])
+
@test.create_stubs({api.quantum: ('subnet_modify',
'subnet_get',)})
def test_subnet_update_post(self):
@@ -661,19 +980,92 @@ class NetworkTests(test.TestCase):
.AndReturn(subnet)
api.quantum.subnet_modify(IsA(http.HttpRequest), subnet.id,
name=subnet.name,
- gateway_ip=subnet.gateway_ip)\
+ gateway_ip=subnet.gateway_ip,
+ enable_dhcp=subnet.enable_dhcp)\
.AndReturn(subnet)
self.mox.ReplayAll()
- formData = {'network_id': subnet.network_id,
- 'subnet_id': subnet.id,
- 'name': subnet.name,
- 'cidr': subnet.cidr,
- 'ip_version': subnet.ip_version,
- 'gateway_ip': subnet.gateway_ip}
+ form_data = form_data_subnet(subnet,
+ allocation_pools=[])
url = reverse('horizon:project:networks:editsubnet',
args=[subnet.network_id, subnet.id])
- res = self.client.post(url, formData)
+ res = self.client.post(url, form_data)
+
+ redir_url = reverse('horizon:project:networks:detail',
+ args=[subnet.network_id])
+ self.assertRedirectsNoFollow(res, redir_url)
+
+ @test.create_stubs({api.quantum: ('subnet_modify',
+ 'subnet_get',)})
+ def test_subnet_update_post(self):
+ subnet = self.subnets.first()
+ api.quantum.subnet_get(IsA(http.HttpRequest), subnet.id)\
+ .AndReturn(subnet)
+ api.quantum.subnet_modify(IsA(http.HttpRequest), subnet.id,
+ name=subnet.name,
+ gateway_ip=subnet.gateway_ip,
+ enable_dhcp=subnet.enable_dhcp,
+ dns_nameservers=[],
+ host_routes=[])\
+ .AndReturn(subnet)
+ self.mox.ReplayAll()
+
+ form_data = form_data_subnet(subnet,
+ allocation_pools=[])
+ url = reverse('horizon:project:networks:editsubnet',
+ args=[subnet.network_id, subnet.id])
+ res = self.client.post(url, form_data)
+
+ redir_url = reverse('horizon:project:networks:detail',
+ args=[subnet.network_id])
+ self.assertRedirectsNoFollow(res, redir_url)
+
+ @test.create_stubs({api.quantum: ('subnet_modify',
+ 'subnet_get',)})
+ def test_subnet_update_post_no_gateway(self):
+ subnet = self.subnets.first()
+ api.quantum.subnet_get(IsA(http.HttpRequest), subnet.id)\
+ .AndReturn(subnet)
+ api.quantum.subnet_modify(IsA(http.HttpRequest), subnet.id,
+ name=subnet.name,
+ gateway_ip=None,
+ enable_dhcp=subnet.enable_dhcp,
+ dns_nameservers=[],
+ host_routes=[])\
+ .AndReturn(subnet)
+ self.mox.ReplayAll()
+
+ form_data = form_data_subnet(subnet,
+ gateway_ip=None,
+ allocation_pools=[])
+ url = reverse('horizon:project:networks:editsubnet',
+ args=[subnet.network_id, subnet.id])
+ res = self.client.post(url, form_data)
+
+ redir_url = reverse('horizon:project:networks:detail',
+ args=[subnet.network_id])
+ self.assertRedirectsNoFollow(res, redir_url)
+
+ @test.create_stubs({api.quantum: ('subnet_modify',
+ 'subnet_get',)})
+ def test_subnet_update_post_with_additional_attributes(self):
+ subnet = self.subnets.list()[1]
+ api.quantum.subnet_get(IsA(http.HttpRequest), subnet.id)\
+ .AndReturn(subnet)
+ api.quantum.subnet_modify(IsA(http.HttpRequest), subnet.id,
+ name=subnet.name,
+ gateway_ip=subnet.gateway_ip,
+ enable_dhcp=False,
+ dns_nameservers=subnet.dns_nameservers,
+ host_routes=subnet.host_routes)\
+ .AndReturn(subnet)
+ self.mox.ReplayAll()
+
+ form_data = form_data_subnet(subnet,
+ enable_dhcp=False)
+ url = reverse('horizon:project:networks:editsubnet',
+ args=[subnet.network_id, subnet.id])
+ res = self.client.post(url, form_data)
redir_url = reverse('horizon:project:networks:detail',
args=[subnet.network_id])
@@ -689,18 +1081,120 @@ class NetworkTests(test.TestCase):
# dummy IPv6 address
gateway_ip = '2001:0DB8:0:CD30:123:4567:89AB:CDEF'
- formData = {'network_id': subnet.network_id,
- 'subnet_id': subnet.id,
- 'name': subnet.name,
- 'cidr': subnet.cidr,
- 'ip_version': subnet.ip_version,
- 'gateway_ip': gateway_ip}
+ form_data = form_data_subnet(subnet, gateway_ip=gateway_ip,
+ allocation_pools=[])
url = reverse('horizon:project:networks:editsubnet',
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.')
+ @test.create_stubs({api.quantum: ('subnet_modify',
+ 'subnet_get',)})
+ def test_subnet_update_post_invalid_nameservers(self):
+ subnet = self.subnets.first()
+ api.quantum.subnet_get(IsA(http.HttpRequest), subnet.id)\
+ .AndReturn(subnet)
+ self.mox.ReplayAll()
+
+ # invalid DNS server address
+ dns_nameservers = ['192.168.0.2', 'invalid_address']
+ form_data = form_data_subnet(subnet, dns_nameservers=dns_nameservers,
+ allocation_pools=[])
+ url = reverse('horizon:project:networks:editsubnet',
+ args=[subnet.network_id, subnet.id])
+ res = self.client.post(url, form_data)
+
+ self.assertContains(res,
+ 'dns_nameservers: Invalid IP address '
+ '(value=%s)' % dns_nameservers[1])
+
+ @test.create_stubs({api.quantum: ('subnet_modify',
+ 'subnet_get',)})
+ def test_subnet_update_post_invalid_routes_destination_only(self):
+ subnet = self.subnets.first()
+ api.quantum.subnet_get(IsA(http.HttpRequest), subnet.id)\
+ .AndReturn(subnet)
+ self.mox.ReplayAll()
+
+ # Start only host_route
+ host_routes = '192.168.0.0/24'
+ form_data = form_data_subnet(subnet,
+ allocation_pools=[],
+ host_routes=host_routes)
+ url = reverse('horizon:project:networks:editsubnet',
+ args=[subnet.network_id, subnet.id])
+ res = self.client.post(url, form_data)
+
+ self.assertContains(res,
+ 'Host Routes format error: '
+ 'Destination CIDR and nexthop must be specified '
+ '(value=%s)' % host_routes)
+
+ @test.create_stubs({api.quantum: ('subnet_modify',
+ 'subnet_get',)})
+ def test_subnet_update_post_invalid_routes_three_entries(self):
+ subnet = self.subnets.first()
+ api.quantum.subnet_get(IsA(http.HttpRequest), subnet.id)\
+ .AndReturn(subnet)
+ self.mox.ReplayAll()
+
+ # host_route with three entries
+ host_routes = 'aaaa,bbbb,cccc'
+ form_data = form_data_subnet(subnet,
+ allocation_pools=[],
+ host_routes=host_routes)
+ url = reverse('horizon:project:networks:editsubnet',
+ args=[subnet.network_id, subnet.id])
+ res = self.client.post(url, form_data)
+
+ self.assertContains(res,
+ 'Host Routes format error: '
+ 'Destination CIDR and nexthop must be specified '
+ '(value=%s)' % host_routes)
+
+ @test.create_stubs({api.quantum: ('subnet_modify',
+ 'subnet_get',)})
+ def test_subnet_update_post_invalid_routes_invalid_destination(self):
+ subnet = self.subnets.first()
+ api.quantum.subnet_get(IsA(http.HttpRequest), subnet.id)\
+ .AndReturn(subnet)
+ self.mox.ReplayAll()
+
+ # invalid destination network
+ host_routes = '172.16.0.0/64,10.0.0.253'
+ form_data = form_data_subnet(subnet,
+ host_routes=host_routes,
+ allocation_pools=[])
+ url = reverse('horizon:project:networks:editsubnet',
+ args=[subnet.network_id, subnet.id])
+ res = self.client.post(url, form_data)
+
+ self.assertContains(res,
+ 'host_routes: Invalid IP address '
+ '(value=%s)' % host_routes.split(',')[0])
+
+ @test.create_stubs({api.quantum: ('subnet_modify',
+ 'subnet_get',)})
+ def test_subnet_update_post_invalid_routes_nexthop_ip_network(self):
+ subnet = self.subnets.first()
+ api.quantum.subnet_get(IsA(http.HttpRequest), subnet.id)\
+ .AndReturn(subnet)
+ self.mox.ReplayAll()
+
+ # nexthop is not an IP address
+ host_routes = '172.16.0.0/24,10.0.0.253/24'
+ form_data = form_data_subnet(subnet,
+ host_routes=host_routes,
+ allocation_pools=[])
+ url = reverse('horizon:project:networks:editsubnet',
+ args=[subnet.network_id, subnet.id])
+ res = self.client.post(url, form_data)
+
+ self.assertContains(res,
+ 'host_routes: Invalid IP address '
+ '(value=%s)' % host_routes.split(',')[1])
+
@test.create_stubs({api.quantum: ('subnet_delete',
'subnet_list',
'network_get',
@@ -720,10 +1214,10 @@ class NetworkTests(test.TestCase):
.AndReturn(self.networks.first())
self.mox.ReplayAll()
- formData = {'action': 'subnets__delete__%s' % subnet.id}
+ form_data = {'action': 'subnets__delete__%s' % subnet.id}
url = reverse('horizon:project:networks:detail',
args=[network_id])
- res = self.client.post(url, formData)
+ res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, url)
@@ -747,13 +1241,16 @@ class NetworkTests(test.TestCase):
.AndReturn(self.networks.first())
self.mox.ReplayAll()
- formData = {'action': 'subnets__delete__%s' % subnet.id}
+ form_data = {'action': 'subnets__delete__%s' % subnet.id}
url = reverse('horizon:project:networks:detail',
args=[network_id])
- res = self.client.post(url, formData)
+ res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, url)
+
+class NetworkPortTests(test.TestCase):
+
@test.create_stubs({api.quantum: ('port_get',)})
def test_port_detail(self):
port = self.ports.first()
@@ -780,3 +1277,65 @@ class NetworkTests(test.TestCase):
args=[port.id]))
self.assertRedirectsNoFollow(res, INDEX_URL)
+
+ @test.create_stubs({api.quantum: ('port_get',)})
+ def test_port_update_get(self):
+ port = self.ports.first()
+ api.quantum.port_get(IsA(http.HttpRequest),
+ port.id)\
+ .AndReturn(port)
+ self.mox.ReplayAll()
+
+ url = reverse('horizon:project:networks:editport',
+ args=[port.network_id, port.id])
+ res = self.client.get(url)
+
+ self.assertTemplateUsed(res, 'project/networks/ports/update.html')
+
+ @test.create_stubs({api.quantum: ('port_get',
+ 'port_modify')})
+ def test_port_update_post(self):
+ port = self.ports.first()
+ api.quantum.port_get(IsA(http.HttpRequest), port.id)\
+ .AndReturn(port)
+ api.quantum.port_modify(IsA(http.HttpRequest), port.id,
+ name=port.name,
+ admin_state_up=port.admin_state_up)\
+ .AndReturn(port)
+ self.mox.ReplayAll()
+
+ form_data = {'network_id': port.network_id,
+ 'port_id': port.id,
+ 'name': port.name,
+ 'admin_state': port.admin_state_up}
+ url = reverse('horizon:project:networks:editport',
+ args=[port.network_id, port.id])
+ res = self.client.post(url, form_data)
+
+ redir_url = reverse('horizon:project:networks:detail',
+ args=[port.network_id])
+ self.assertRedirectsNoFollow(res, redir_url)
+
+ @test.create_stubs({api.quantum: ('port_get',
+ 'port_modify')})
+ def test_port_update_post_exception(self):
+ port = self.ports.first()
+ api.quantum.port_get(IsA(http.HttpRequest), port.id)\
+ .AndReturn(port)
+ api.quantum.port_modify(IsA(http.HttpRequest), port.id,
+ name=port.name,
+ admin_state_up=port.admin_state_up)\
+ .AndRaise(self.exceptions.quantum)
+ self.mox.ReplayAll()
+
+ form_data = {'network_id': port.network_id,
+ 'port_id': port.id,
+ 'name': port.name,
+ 'admin_state': port.admin_state_up}
+ url = reverse('horizon:project:networks:editport',
+ args=[port.network_id, port.id])
+ res = self.client.post(url, form_data)
+
+ redir_url = reverse('horizon:project:networks:detail',
+ args=[port.network_id])
+ self.assertRedirectsNoFollow(res, redir_url)
diff --git a/openstack_dashboard/dashboards/project/networks/urls.py b/openstack_dashboard/dashboards/project/networks/urls.py
index ee856cb37..06f53b79e 100644
--- a/openstack_dashboard/dashboards/project/networks/urls.py
+++ b/openstack_dashboard/dashboards/project/networks/urls.py
@@ -20,6 +20,7 @@ from .views import IndexView, CreateView, DetailView, UpdateView
from .subnets.views import CreateView as AddSubnetView
from .subnets.views import UpdateView as EditSubnetView
from .subnets import urls as subnet_urls
+from .ports.views import UpdateView as EditPortView
from .ports import urls as port_urls
@@ -35,5 +36,7 @@ urlpatterns = patterns('',
name='addsubnet'),
url(r'^(?P[^/]+)/subnets/(?P[^/]+)/update$',
EditSubnetView.as_view(), name='editsubnet'),
+ url(r'^(?P[^/]+)/ports/(?P[^/]+)/update$',
+ EditPortView.as_view(), name='editport'),
url(r'^subnets/', include(subnet_urls, namespace='subnets')),
url(r'^ports/', include(port_urls, namespace='ports')))
diff --git a/openstack_dashboard/dashboards/project/networks/views.py b/openstack_dashboard/dashboards/project/networks/views.py
index dbc5d18fa..a4be61841 100644
--- a/openstack_dashboard/dashboards/project/networks/views.py
+++ b/openstack_dashboard/dashboards/project/networks/views.py
@@ -91,7 +91,8 @@ class UpdateView(forms.ModalFormView):
network = self._get_object()
return {'network_id': network['id'],
'tenant_id': network['tenant_id'],
- 'name': network['name']}
+ 'name': network['name'],
+ 'admin_state': network['admin_state_up']}
class DetailView(tables.MultiTableView):
diff --git a/openstack_dashboard/dashboards/project/networks/workflows.py b/openstack_dashboard/dashboards/project/networks/workflows.py
index 54d4a9bf9..41283ece9 100644
--- a/openstack_dashboard/dashboards/project/networks/workflows.py
+++ b/openstack_dashboard/dashboards/project/networks/workflows.py
@@ -23,6 +23,7 @@ from django.utils.translation import ugettext as _
from horizon import exceptions
from horizon import forms
+from horizon import messages
from horizon import workflows
from horizon.utils import fields
@@ -34,8 +35,12 @@ LOG = logging.getLogger(__name__)
class CreateNetworkInfoAction(workflows.Action):
net_name = forms.CharField(max_length=255,
- label=_("Network Name (optional)"),
+ label=_("Network Name"),
+ help_text=_("Network Name. This field is "
+ "optional."),
required=False)
+ admin_state = forms.BooleanField(label=_("Admin State"),
+ initial=True, required=False)
class Meta:
name = ("Network")
@@ -46,14 +51,16 @@ class CreateNetworkInfoAction(workflows.Action):
class CreateNetworkInfo(workflows.Step):
action_class = CreateNetworkInfoAction
- contributes = ("net_name",)
+ contributes = ("net_name", "admin_state")
class CreateSubnetInfoAction(workflows.Action):
with_subnet = forms.BooleanField(label=_("Create Subnet"),
initial=True, required=False)
subnet_name = forms.CharField(max_length=255,
- label=_("Subnet Name (optional)"),
+ label=_("Subnet Name"),
+ help_text=_("Subnet Name. This field is "
+ "optional."),
required=False)
cidr = fields.IPField(label=_("Network Address"),
required=False,
@@ -64,13 +71,21 @@ class CreateSubnetInfoAction(workflows.Action):
mask=True)
ip_version = forms.ChoiceField(choices=[(4, 'IPv4'), (6, 'IPv6')],
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.1)"),
- version=fields.IPv4 | fields.IPv6,
- mask=False)
+ gateway_ip = fields.IPField(
+ label=_("Gateway IP (optional)"),
+ required=False,
+ initial="",
+ help_text=_("IP address of Gateway (e.g. 192.168.0.254) "
+ "The default value is the first IP of the "
+ "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:
name = ("Subnet")
@@ -79,13 +94,12 @@ class CreateSubnetInfoAction(workflows.Action):
'specified. If you wish to create a network WITHOUT a '
'subnet, uncheck the "Create Subnet" checkbox.')
- def clean(self):
- cleaned_data = super(CreateSubnetInfoAction, self).clean()
- with_subnet = cleaned_data.get('with_subnet')
+ def _check_subnet_data(self, cleaned_data, is_create=True):
cidr = cleaned_data.get('cidr')
ip_version = int(cleaned_data.get('ip_version'))
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 '
'clear "Create Subnet" checkbox.')
raise forms.ValidationError(msg)
@@ -93,17 +107,126 @@ class CreateSubnetInfoAction(workflows.Action):
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 not no_gateway and gateway_ip:
if netaddr.IPAddress(gateway_ip).version is not ip_version:
msg = _('Gateway IP and IP version are inconsistent.')
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
class CreateSubnetInfo(workflows.Step):
action_class = CreateSubnetInfoAction
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 "
+ "<start_ip_address>,<end_ip_address> "
+ "(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 <destination_cidr>,<nexthop> "
+ "(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):
@@ -112,51 +235,130 @@ class CreateNetwork(workflows.Workflow):
finalize_button_name = _("Create")
success_message = _('Created network "%s".')
failure_message = _('Unable to create network "%s".')
- success_url = "horizon:project:networks:index"
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):
name = self.context.get('net_name') or self.context.get('net_id', '')
return message % name
- def handle(self, request, data):
- # create the network
+ def _create_network(self, request, data):
try:
- network = api.quantum.network_create(request,
- name=data['net_name'])
+ params = {'name': data['net_name'],
+ 'admin_state_up': data['admin_state']}
+ network = api.quantum.network_create(request, **params)
network.set_id_as_name_if_empty()
self.context['net_id'] = network.id
msg = _('Network "%s" was successfully created.') % network.name
LOG.debug(msg)
- except:
- msg = _('Failed to create network "%s".') % data['net_name']
+ return network
+ except Exception as e:
+ msg = (_('Failed to create network "%(network)s": %(reason)s') %
+ {"network": data['net_name'], "reason": e})
LOG.info(msg)
- redirect = reverse('horizon:project:networks:index')
+ redirect = self.get_failure_url()
exceptions.handle(request, msg, redirect=redirect)
return False
- # If we do not need to create a subnet, return here.
- if not data['with_subnet']:
- return True
+ def _setup_subnet_parameters(self, params, data, is_create=True):
+ """Setup subnet parameters
- # 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:
- params = {'network_id': network.id,
+ params = {'network_id': network_id,
'name': data['subnet_name'],
'cidr': data['cidr'],
'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']
- 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']
LOG.debug(msg)
- except Exception:
- msg = _('Failed to create subnet "%(sub)s" for network "%(net)s".')
- redirect = reverse('horizon:project:networks:index')
+ return subnet
+ except Exception as e:
+ 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,
- msg % {"sub": data['cidr'], "net": network.id},
+ msg % {"sub": data['cidr'], "net": network_name,
+ "reason": e},
redirect=redirect)
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
diff --git a/openstack_dashboard/test/test_data/quantum_data.py b/openstack_dashboard/test/test_data/quantum_data.py
index 23b896fd9..23e138127 100644
--- a/openstack_dashboard/test/test_data/quantum_data.py
+++ b/openstack_dashboard/test/test_data/quantum_data.py
@@ -37,9 +37,12 @@ def data(TEST):
'status': 'ACTIVE',
'subnets': ['e8abc972-eb0c-41f1-9edd-4bc6e3bcd8c9'],
'tenant_id': '1',
+ 'router:external': False,
'shared': False}
subnet_dict = {'allocation_pools': [{'end': '10.0.0.254',
'start': '10.0.0.2'}],
+ 'dns_nameservers': [],
+ 'host_routes': [],
'cidr': '10.0.0.0/24',
'enable_dhcp': True,
'gateway_ip': '10.0.0.1',
@@ -50,6 +53,7 @@ def data(TEST):
'tenant_id': network_dict['tenant_id']}
port_dict = {'admin_state_up': True,
'device_id': 'af75c8e5-a1cc-4567-8d04-44fcd6922890',
+ 'device_owner': 'network:dhcp',
'fixed_ips': [{'ip_address': '10.0.0.3',
'subnet_id': subnet_dict['id']}],
'id': '3ec7f3db-cb2f-4a34-ab6b-69a64d3f008c',
@@ -77,9 +81,15 @@ def data(TEST):
'status': 'ACTIVE',
'subnets': ['3f7c5d79-ee55-47b0-9213-8e669fb03009'],
'tenant_id': '2',
+ 'router:external': True,
'shared': True}
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',
'enable_dhcp': True,
'gateway_ip': '172.16.88.1',
@@ -90,6 +100,7 @@ def data(TEST):
'tenant_id': network_dict['tenant_id']}
port_dict = {'admin_state_up': True,
'device_id': '40e536b1-b9fd-4eb7-82d6-84db5d65a2ac',
+ 'device_owner': 'compute:nova',
'fixed_ips': [{'ip_address': '172.16.88.3',
'subnet_id': subnet_dict['id']}],
'id': '7e6ce62c-7ea2-44f8-b6b4-769af90a8406',