Improve Quantum panels to Folsom advanced features

Implements blueprint improve-quantum-summary-table

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

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

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

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

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

View File

@ -50,19 +50,19 @@ class QuantumAPIDictWrapper(APIDictWrapper):
class Network(QuantumAPIDictWrapper):
"""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'] = \

View File

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

View File

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

View File

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

View File

@ -1,53 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 NEC Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import forms
from horizon import exceptions
from openstack_dashboard import api
from openstack_dashboard.dashboards.project.networks.subnets import \
forms as user_forms
LOG = logging.getLogger(__name__)
class CreateSubnet(user_forms.CreateSubnet):
failure_url = 'horizon:admin:networks:detail'
def handle(self, request, data):
try:
# We must specify tenant_id of the network which a subnet is
# created for if admin user does not belong to the tenant.
network = api.quantum.network_get(request, data['network_id'])
data['tenant_id'] = network.tenant_id
except:
msg = _('Failed to retrieve network %s for a subnet') \
% data['network_id']
LOG.info(msg)
redirect = reverse(self.failure_url, args=[data['network_id']])
exceptions.handle(request, msg, redirect=redirect)
return super(CreateSubnet, self).handle(request, data)
class UpdateSubnet(user_forms.UpdateSubnet):
tenant_id = forms.CharField(widget=forms.HiddenInput())
failure_url = 'horizon:admin:networks:detail'

View File

@ -23,81 +23,20 @@ from horizon import exceptions
from horizon import forms
from 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']}

View File

@ -0,0 +1,60 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 NEC Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from horizon import exceptions
from openstack_dashboard import api
from openstack_dashboard.dashboards.project.networks.subnets \
import workflows as project_workflows
LOG = logging.getLogger(__name__)
class CreateSubnet(project_workflows.CreateSubnet):
def get_success_url(self):
return reverse("horizon:admin:networks:detail",
args=(self.context.get('network_id'),))
def get_failure_url(self):
return reverse("horizon:admin:networks:detail",
args=(self.context.get('network_id'),))
def handle(self, request, data):
try:
# We must specify tenant_id of the network which a subnet is
# created for if admin user does not belong to the tenant.
network = api.quantum.network_get(request,
self.context['network_id'])
except:
msg = (_('Failed to retrieve network %s for a subnet') %
data['network_id'])
LOG.info(msg)
redirect = self.get_failure_url()
exceptions.handle(request, msg, redirect=redirect)
subnet = self._create_subnet(request, data,
tenant_id=network.tenant_id)
return True if subnet else False
class UpdateSubnet(project_workflows.UpdateSubnet):
success_url = "horizon:admin:networks:detail"
failure_url = "horizon:admin:networks:detail"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,56 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 NEC Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from openstack_dashboard import api
LOG = logging.getLogger(__name__)
class UpdatePort(forms.SelfHandlingForm):
network_id = forms.CharField(widget=forms.HiddenInput())
port_id = forms.CharField(widget=forms.HiddenInput())
name = forms.CharField(max_length=255,
label=_("Name"),
required=False)
admin_state = forms.BooleanField(label=_("Admin State"), required=False)
failure_url = 'horizon:project:networks:detail'
def handle(self, request, data):
try:
LOG.debug('params = %s' % data)
port = api.quantum.port_modify(request, data['port_id'],
name=data['name'],
admin_state_up=data['admin_state'])
msg = _('Port %s was successfully updated.') % data['port_id']
LOG.debug(msg)
messages.success(request, msg)
return port
except Exception:
msg = _('Failed to update port %s') % data['port_id']
LOG.info(msg)
redirect = reverse(self.failure_url,
args=[data['network_id']])
exceptions.handle(request, msg, redirect=redirect)

View File

@ -16,6 +16,7 @@
import logging
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,)

View File

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

View File

@ -1,139 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 NEC Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
import netaddr
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import forms
from horizon import messages
from horizon import exceptions
from horizon.utils import fields
from openstack_dashboard import api
LOG = logging.getLogger(__name__)
class CreateSubnet(forms.SelfHandlingForm):
network_name = forms.CharField(label=_("Network Name"),
required=False,
widget=forms.TextInput(
attrs={'readonly': 'readonly'}))
network_id = forms.CharField(label=_("Network ID"),
widget=forms.TextInput(
attrs={'readonly': 'readonly'}))
name = forms.CharField(max_length=255,
label=_("Name"),
required=False)
cidr = fields.IPField(label=_("Network Address"),
required=True,
initial="",
help_text=_("Network address in CIDR format "
"(e.g. 192.168.0.0/24)"),
version=fields.IPv4 | fields.IPv6,
mask=True)
ip_version = forms.ChoiceField(choices=[(4, 'IPv4'), (6, 'IPv6')],
label=_("IP Version"))
gateway_ip = fields.IPField(label=_("Gateway IP"),
required=False,
initial="",
help_text=_("IP address of Gateway "
"(e.g. 192.168.0.1)"),
version=fields.IPv4 | fields.IPv6,
mask=False)
failure_url = 'horizon:project:networks:detail'
def clean(self):
cleaned_data = super(CreateSubnet, self).clean()
cidr = cleaned_data.get('cidr')
ip_version = int(cleaned_data.get('ip_version'))
gateway_ip = cleaned_data.get('gateway_ip')
if cidr:
if netaddr.IPNetwork(cidr).version is not ip_version:
msg = _('Network Address and IP version are inconsistent.')
raise forms.ValidationError(msg)
if gateway_ip:
if netaddr.IPAddress(gateway_ip).version is not ip_version:
msg = _('Gateway IP and IP version are inconsistent.')
raise forms.ValidationError(msg)
return cleaned_data
def handle(self, request, data):
try:
LOG.debug('params = %s' % data)
data['ip_version'] = int(data['ip_version'])
if not data['gateway_ip']:
del data['gateway_ip']
subnet = api.quantum.subnet_create(request, **data)
msg = _('Subnet %s was successfully created.') % data['cidr']
LOG.debug(msg)
messages.success(request, msg)
return subnet
except Exception:
msg = _('Failed to create subnet %s') % data['cidr']
LOG.info(msg)
redirect = reverse(self.failure_url, args=[data['network_id']])
exceptions.handle(request, msg, redirect=redirect)
class UpdateSubnet(forms.SelfHandlingForm):
network_id = forms.CharField(widget=forms.HiddenInput())
subnet_id = forms.CharField(widget=forms.HiddenInput())
cidr = forms.CharField(widget=forms.HiddenInput())
ip_version = forms.CharField(widget=forms.HiddenInput())
name = forms.CharField(max_length=255,
label=_("Name"),
required=False)
gateway_ip = fields.IPField(label=_("Gateway IP"),
required=True,
initial="",
help_text=_("IP address of Gateway "
"(e.g. 192.168.0.1)"),
version=fields.IPv4 | fields.IPv6,
mask=False)
failure_url = 'horizon:project:networks:detail'
def clean(self):
cleaned_data = super(UpdateSubnet, self).clean()
ip_version = int(cleaned_data.get('ip_version'))
gateway_ip = cleaned_data.get('gateway_ip')
if gateway_ip:
if netaddr.IPAddress(gateway_ip).version is not ip_version:
msg = _('Gateway IP and IP version are inconsistent.')
raise forms.ValidationError(msg)
return cleaned_data
def handle(self, request, data):
try:
LOG.debug('params = %s' % data)
params = {'name': data['name']}
params['gateway_ip'] = data['gateway_ip']
subnet = api.quantum.subnet_modify(request, data['subnet_id'],
name=data['name'],
gateway_ip=data['gateway_ip'])
msg = _('Subnet %s was successfully updated.') % data['cidr']
LOG.debug(msg)
messages.success(request, msg)
return subnet
except Exception:
msg = _('Failed to update subnet %s') % data['cidr']
LOG.info(msg)
redirect = reverse(self.failure_url, args=[data['network_id']])
exceptions.handle(request, msg, redirect=redirect)

View File

@ -22,26 +22,21 @@ import logging
from django.core.urlresolvers import reverse_lazy, reverse
from django.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):

View File

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

View File

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

View File

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

View File

@ -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 %}
<div class="left">
<dl>
<dt>{% trans "ID" %}</dt>
<dd>{{ subnet_id }}</dd>
<dt>{% trans "Network Address" %}</dt>
<dd>{{ cidr }}</dd>
<dt>{% trans "IP version" %}</dt>
<dd>{{ ip_version }}</dd>
<dd>{{ port_id }}</dd>
</dl>
<hr>
<fieldset>
@ -23,7 +19,7 @@
</div>
<div class="right">
<h3>{% trans "Description:" %}</h3>
<p>{% trans "You may update the editable properties of your subnet here." %}</p>
<p>{% trans "You may update the editable properties of your port here." %}</p>
</div>
{% endblock %}

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -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 "
"&lt;start_ip_address&gt;,&lt;end_ip_address&gt; "
"(e.g., 192.168.1.100,192.168.1.120) "
"and one entry per line."),
required=False)
dns_nameservers = forms.CharField(
widget=forms.widgets.Textarea(),
label=_("DNS Name Servers"),
help_text=_("IP address list of DNS name servers for this subnet. "
"One entry per line."),
required=False)
host_routes = forms.CharField(
widget=forms.widgets.Textarea(),
label=_("Host Routes"),
help_text=_("Additional routes announced to the hosts. "
"Each entry is &lt;destination_cidr&gt;,&lt;nexthop&gt; "
"(e.g., 192.168.200.0/24,10.56.1.254)"
"and one entry per line."),
required=False)
class Meta:
name = ("Subnet Detail")
help_text = _('You can specify additional attributes for the subnet.')
def _convert_ip_address(self, ip, field_name):
try:
return netaddr.IPAddress(ip)
except (netaddr.AddrFormatError, ValueError):
msg = _('%(field_name)s: Invalid IP address '
'(value=%(ip)s)') % locals()
raise forms.ValidationError(msg)
def _convert_ip_network(self, network, field_name):
try:
return netaddr.IPNetwork(network)
except (netaddr.AddrFormatError, ValueError):
msg = _('%(field_name)s: Invalid IP address '
'(value=%(network)s)') % locals()
raise forms.ValidationError(msg)
def _check_allocation_pools(self, allocation_pools):
for p in allocation_pools.split('\n'):
p = p.strip()
if not p:
continue
pool = p.split(',')
if len(pool) != 2:
msg = _('Start and end addresses must be specified '
'(value=%s)') % p
raise forms.ValidationError(msg)
start, end = [self._convert_ip_address(ip, "allocation_pools")
for ip in pool]
if start > end:
msg = _('Start address is larger than end address '
'(value=%s)') % p
raise forms.ValidationError(msg)
def _check_dns_nameservers(self, dns_nameservers):
for ns in dns_nameservers.split('\n'):
ns = ns.strip()
if not ns:
continue
self._convert_ip_address(ns, "dns_nameservers")
def _check_host_routes(self, host_routes):
for r in host_routes.split('\n'):
r = r.strip()
if not r:
continue
route = r.split(',')
if len(route) != 2:
msg = _('Host Routes format error: '
'Destination CIDR and nexthop must be specified '
'(value=%s)') % r
raise forms.ValidationError(msg)
dest = self._convert_ip_network(route[0], "host_routes")
nexthop = self._convert_ip_address(route[1], "host_routes")
def clean(self):
cleaned_data = super(CreateSubnetDetailAction, self).clean()
self._check_allocation_pools(cleaned_data.get('allocation_pools'))
self._check_host_routes(cleaned_data.get('host_routes'))
self._check_dns_nameservers(cleaned_data.get('dns_nameservers'))
return cleaned_data
class CreateSubnetDetail(workflows.Step):
action_class = CreateSubnetDetailAction
contributes = ("enable_dhcp", "allocation_pools",
"dns_nameservers", "host_routes")
class CreateNetwork(workflows.Workflow):
@ -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

View File

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