Refactor the create port form to workflow
The neutron API allows the users to specify security group(s) when creating a port [1], but at the moment the Horizon can only modify the security group of a port by 'Edit Port' row action. As with edit port workflow, refactoring the creation of port form to workflow is to support the creation of ports as well as the assignment of security groups. Since the change may be more, this patch only contains reconstruct the form, support the security groups will be submitted in another patch. [1] https://developer.openstack.org/api-ref/network/v2/index.html#create-port Change-Id: Ie7e5242cc885b6eb0316f695453b0d2844360d67 Related-bug: #1746985
This commit is contained in:
parent
17b2c25d50
commit
88d78a489b
@ -1,82 +0,0 @@
|
||||
# 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
|
||||
from openstack_dashboard.dashboards.project.networks.ports \
|
||||
import forms as project_forms
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreatePort(project_forms.CreatePort):
|
||||
binding__host_id = forms.CharField(
|
||||
label=_("Binding: Host"),
|
||||
help_text=_("The ID of the host where the port is allocated. In some "
|
||||
"cases, different implementations can run on different "
|
||||
"hosts."),
|
||||
required=False)
|
||||
failure_url = 'horizon:admin:networks:detail'
|
||||
|
||||
def handle(self, request, data):
|
||||
network_id = self.initial['network_id']
|
||||
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.neutron.network_get(request, network_id)
|
||||
params = {
|
||||
'tenant_id': network.tenant_id,
|
||||
'network_id': network_id,
|
||||
'admin_state_up': data['admin_state'],
|
||||
'name': data['name'],
|
||||
'device_id': data['device_id'],
|
||||
'device_owner': data['device_owner'],
|
||||
'binding__host_id': data['binding__host_id']
|
||||
}
|
||||
|
||||
if data.get('specify_ip') == 'subnet_id':
|
||||
if data.get('subnet_id'):
|
||||
params['fixed_ips'] = [{"subnet_id": data['subnet_id']}]
|
||||
elif data.get('specify_ip') == 'fixed_ip':
|
||||
if data.get('fixed_ip'):
|
||||
params['fixed_ips'] = [{"ip_address": data['fixed_ip']}]
|
||||
|
||||
if data.get('binding__vnic_type'):
|
||||
params['binding__vnic_type'] = data['binding__vnic_type']
|
||||
|
||||
if data.get('mac_state'):
|
||||
params['mac_learning_enabled'] = data['mac_state']
|
||||
|
||||
if 'port_security_enabled' in data:
|
||||
params['port_security_enabled'] = data['port_security_enabled']
|
||||
|
||||
port = api.neutron.port_create(request, **params)
|
||||
msg = _('Port %s was successfully created.') % port['id']
|
||||
messages.success(request, msg)
|
||||
return port
|
||||
except Exception as e:
|
||||
LOG.info('Failed to create a port for network %(id)s: %(exc)s',
|
||||
{'id': network_id, 'exc': e})
|
||||
msg = _('Failed to create a port for network %s') % network_id
|
||||
redirect = reverse(self.failure_url, args=(network_id,))
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
@ -113,7 +113,7 @@ class NetworkPortTests(test.BaseAdminViewTests):
|
||||
args=[network.id])
|
||||
res = self.client.get(url)
|
||||
|
||||
self.assertTemplateUsed(res, 'admin/networks/ports/create.html')
|
||||
self.assertTemplateUsed(res, views.WorkflowView.template_name)
|
||||
|
||||
@test.create_stubs({api.neutron: ('network_get',
|
||||
'is_extension_supported',
|
||||
@ -137,12 +137,6 @@ class NetworkPortTests(test.BaseAdminViewTests):
|
||||
port_security=False):
|
||||
network = self.networks.first()
|
||||
port = self.ports.first()
|
||||
api.neutron.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
api.neutron.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
api.neutron.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
@ -206,13 +200,7 @@ class NetworkPortTests(test.BaseAdminViewTests):
|
||||
port = self.ports.first()
|
||||
api.neutron.network_get(IsA(http.HttpRequest),
|
||||
network.id) \
|
||||
.AndReturn(self.networks.first())
|
||||
api.neutron.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
api.neutron.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
.MultipleTimes().AndReturn(self.networks.first())
|
||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||
'mac-learning')\
|
||||
.AndReturn(True)
|
||||
@ -280,12 +268,6 @@ class NetworkPortTests(test.BaseAdminViewTests):
|
||||
port_security=False):
|
||||
network = self.networks.first()
|
||||
port = self.ports.first()
|
||||
api.neutron.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
api.neutron.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
api.neutron.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
|
@ -15,14 +15,6 @@
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon.utils import memoized
|
||||
|
||||
from openstack_dashboard import api
|
||||
|
||||
from openstack_dashboard.dashboards.admin.networks.ports \
|
||||
import forms as ports_forms
|
||||
from openstack_dashboard.dashboards.admin.networks.ports \
|
||||
import tables as ports_tables
|
||||
from openstack_dashboard.dashboards.admin.networks.ports \
|
||||
@ -33,42 +25,9 @@ from openstack_dashboard.dashboards.project.networks.ports \
|
||||
import views as project_views
|
||||
|
||||
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = ports_forms.CreatePort
|
||||
form_id = "create_port_form"
|
||||
submit_label = _("Create Port")
|
||||
submit_url = "horizon:admin:networks:addport"
|
||||
page_title = _("Create Port")
|
||||
template_name = 'admin/networks/ports/create.html'
|
||||
url = 'horizon:admin:networks:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.url,
|
||||
args=(self.kwargs['network_id'],))
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_object(self):
|
||||
try:
|
||||
network_id = self.kwargs["network_id"]
|
||||
return api.neutron.network_get(self.request, network_id)
|
||||
except Exception:
|
||||
redirect = reverse(self.url,
|
||||
args=(self.kwargs['network_id'],))
|
||||
msg = _("Unable to retrieve network.")
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CreateView, self).get_context_data(**kwargs)
|
||||
context['network'] = self.get_object()
|
||||
args = (self.kwargs['network_id'],)
|
||||
context['submit_url'] = reverse(self.submit_url, args=args)
|
||||
context['cancel_url'] = reverse(self.url, args=args)
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
network = self.get_object()
|
||||
return {"network_id": self.kwargs['network_id'],
|
||||
"network_name": network.name}
|
||||
class CreateView(project_views.CreateView):
|
||||
workflow_class = admin_workflows.CreatePort
|
||||
failure_url = 'horizon:admin:networks:detail'
|
||||
|
||||
|
||||
class DetailView(project_views.DetailView):
|
||||
|
@ -27,6 +27,41 @@ from openstack_dashboard.dashboards.project.networks.ports \
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreatePortInfoAction(project_workflow.CreatePortInfoAction):
|
||||
binding__host_id = forms.CharField(
|
||||
label=_("Binding: Host"),
|
||||
help_text=_("The ID of the host where the port is allocated. In some "
|
||||
"cases, different implementations can run on different "
|
||||
"hosts."),
|
||||
required=False)
|
||||
|
||||
class Meta(object):
|
||||
name = _("Info")
|
||||
slug = 'create_info'
|
||||
help_text_template = 'project/networks/ports/_create_port_help.html'
|
||||
|
||||
|
||||
class CreatePortInfo(project_workflow.CreatePortInfo):
|
||||
action_class = CreatePortInfoAction
|
||||
depends_on = ("network_id", "target_tenant_id")
|
||||
contributes = (project_workflow.CreatePortInfo.contributes
|
||||
+ ['binding__host_id'])
|
||||
|
||||
|
||||
class CreatePort(project_workflow.CreatePort):
|
||||
default_steps = (CreatePortInfo,)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse("horizon:admin:networks:detail",
|
||||
args=(self.context['network_id'],))
|
||||
|
||||
def _construct_parameters(self, context):
|
||||
params = super(CreatePort, self)._construct_parameters(context)
|
||||
params.update({'tenant_id': context['target_tenant_id'],
|
||||
'binding__host_id': context['binding__host_id']})
|
||||
return params
|
||||
|
||||
|
||||
class UpdatePortInfoAction(project_workflow.UpdatePortInfoAction):
|
||||
device_id = forms.CharField(
|
||||
max_length=100, label=_("Device ID"),
|
||||
|
@ -1,7 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% trans "You can create a port for the network. If you specify device ID to be attached, the device specified will be attached to the port created."%}</p>
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create Port" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include "admin/networks/ports/_create.html" %}
|
||||
{% endblock %}
|
@ -1,220 +0,0 @@
|
||||
# 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.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from neutronclient.common import exceptions as neutron_exc
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
from openstack_dashboard import api
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreatePort(forms.SelfHandlingForm):
|
||||
name = forms.CharField(max_length=255,
|
||||
label=_("Name"),
|
||||
required=False)
|
||||
admin_state = forms.BooleanField(label=_("Enable 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=_("Owner of the device attached to the port"),
|
||||
required=False)
|
||||
specify_ip = forms.ThemableChoiceField(
|
||||
label=_("Specify IP address or subnet"),
|
||||
help_text=_("To specify a subnet or a fixed IP, select any options."),
|
||||
required=False,
|
||||
choices=[('', _("Unspecified")),
|
||||
('subnet_id', _("Subnet")),
|
||||
('fixed_ip', _("Fixed IP Address"))],
|
||||
widget=forms.ThemableSelectWidget(attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'specify_ip',
|
||||
}))
|
||||
subnet_id = forms.ThemableChoiceField(
|
||||
label=_("Subnet"),
|
||||
required=False,
|
||||
widget=forms.ThemableSelectWidget(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'specify_ip',
|
||||
'data-specify_ip-subnet_id': _('Subnet'),
|
||||
}))
|
||||
fixed_ip = forms.IPField(
|
||||
label=_("Fixed IP Address"),
|
||||
required=False,
|
||||
help_text=_("Specify the subnet IP address for the new port"),
|
||||
version=forms.IPv4 | forms.IPv6,
|
||||
widget=forms.TextInput(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'specify_ip',
|
||||
'data-specify_ip-fixed_ip': _('Fixed IP Address'),
|
||||
}))
|
||||
mac_address = forms.MACAddressField(
|
||||
label=_("MAC Address"),
|
||||
required=False,
|
||||
help_text=_("Specify the MAC address for the new port"))
|
||||
mac_state = forms.BooleanField(
|
||||
label=_("MAC Learning State"), initial=False,
|
||||
required=False)
|
||||
port_security_enabled = forms.BooleanField(
|
||||
label=_("Port Security"),
|
||||
help_text=_("Enable anti-spoofing rules for the port"),
|
||||
initial=True,
|
||||
required=False)
|
||||
binding__vnic_type = forms.ThemableChoiceField(
|
||||
label=_("VNIC Type"),
|
||||
help_text=_("The VNIC type that is bound to the network port"),
|
||||
required=False)
|
||||
|
||||
failure_url = 'horizon:project:networks:detail'
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(CreatePort, self).__init__(request, *args, **kwargs)
|
||||
|
||||
# prepare subnet choices and input area for each subnet
|
||||
subnet_choices = self._get_subnet_choices(kwargs['initial'])
|
||||
if subnet_choices:
|
||||
subnet_choices.insert(0, ('', _("Select a subnet")))
|
||||
self.fields['subnet_id'].choices = subnet_choices
|
||||
else:
|
||||
self.fields['specify_ip'].widget = forms.HiddenInput()
|
||||
self.fields['subnet_id'].widget = forms.HiddenInput()
|
||||
self.fields['fixed_ip'].widget = forms.HiddenInput()
|
||||
|
||||
self._hide_field_if_not_supported(
|
||||
request, 'mac_state', 'mac-learning',
|
||||
_("Unable to retrieve MAC learning state"))
|
||||
self._hide_field_if_not_supported(
|
||||
request, 'port_security_enabled', 'port-security',
|
||||
_("Unable to retrieve port security state"))
|
||||
|
||||
self._populate_vnic_type_choices(request)
|
||||
|
||||
def _hide_field_if_not_supported(self, request, field, extension_alias,
|
||||
failure_message):
|
||||
is_supproted = False
|
||||
try:
|
||||
is_supproted = api.neutron.is_extension_supported(
|
||||
request, extension_alias)
|
||||
except Exception:
|
||||
exceptions.handle(self.request, failure_message)
|
||||
if not is_supproted:
|
||||
del self.fields[field]
|
||||
return is_supproted
|
||||
|
||||
def _populate_vnic_type_choices(self, request):
|
||||
neutron_settings = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {})
|
||||
supported_vnic_types = neutron_settings.get('supported_vnic_types',
|
||||
['*'])
|
||||
# When a list of VNIC types is empty, hide the corresponding field.
|
||||
if not supported_vnic_types:
|
||||
del self.fields['binding__vnic_type']
|
||||
return
|
||||
|
||||
binding_supported = self._hide_field_if_not_supported(
|
||||
request, 'binding__vnic_type', 'binding',
|
||||
_("Unable to verify the VNIC types extension in Neutron"))
|
||||
if not binding_supported:
|
||||
# binding__vnic_type field is already deleted, so return here
|
||||
return
|
||||
|
||||
if supported_vnic_types == ['*']:
|
||||
vnic_type_choices = api.neutron.VNIC_TYPES
|
||||
else:
|
||||
vnic_type_choices = [
|
||||
vnic_type for vnic_type in api.neutron.VNIC_TYPES
|
||||
if vnic_type[0] in supported_vnic_types
|
||||
]
|
||||
self.fields['binding__vnic_type'].choices = vnic_type_choices
|
||||
|
||||
def _get_subnet_choices(self, kwargs):
|
||||
try:
|
||||
network_id = kwargs['network_id']
|
||||
network = api.neutron.network_get(self.request, network_id)
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
# NOTE(amotoki): When a user cannot retrieve a subnet info,
|
||||
# subnet ID is stored in network.subnets field.
|
||||
# If so, we skip such subnet as subnet choices.
|
||||
# This happens usually for external networks.
|
||||
# TODO(amotoki): Ideally it is better to disable/hide
|
||||
# Create Port button in the port table, but as of Pike
|
||||
# the default neutron policy.json for "create_port" is empty
|
||||
# and there seems no appropriate policy. This is a dirty hack.
|
||||
return [(subnet.id, '%s %s' % (subnet.name_or_id, subnet.cidr))
|
||||
for subnet in network.subnets
|
||||
if isinstance(subnet, api.neutron.Subnet)]
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
params = {
|
||||
'network_id': self.initial['network_id'],
|
||||
'admin_state_up': data['admin_state'],
|
||||
'name': data['name'],
|
||||
'device_id': data['device_id'],
|
||||
'device_owner': data['device_owner']
|
||||
}
|
||||
|
||||
if data.get('specify_ip') == 'subnet_id':
|
||||
if data.get('subnet_id'):
|
||||
params['fixed_ips'] = [{"subnet_id": data['subnet_id']}]
|
||||
elif data.get('specify_ip') == 'fixed_ip':
|
||||
if data.get('fixed_ip'):
|
||||
params['fixed_ips'] = [{"ip_address": data['fixed_ip']}]
|
||||
|
||||
if data.get('binding__vnic_type'):
|
||||
params['binding__vnic_type'] = data['binding__vnic_type']
|
||||
if data.get('mac_state'):
|
||||
params['mac_learning_enabled'] = data['mac_state']
|
||||
if 'port_security_enabled' in data:
|
||||
params['port_security_enabled'] = data['port_security_enabled']
|
||||
|
||||
# Send mac_address only when it is specified.
|
||||
if data['mac_address']:
|
||||
params['mac_address'] = data['mac_address']
|
||||
|
||||
port = api.neutron.port_create(request, **params)
|
||||
if port['name']:
|
||||
msg = _('Port %s was successfully created.') % port['name']
|
||||
else:
|
||||
msg = _('Port %s was successfully created.') % port['id']
|
||||
messages.success(request, msg)
|
||||
return port
|
||||
except Exception as e:
|
||||
LOG.info('Failed to create a port for network %(id)s: %(exc)s',
|
||||
{'id': self.initial['network_id'], 'exc': e})
|
||||
if isinstance(e, neutron_exc.Forbidden):
|
||||
msg = (_('You are not allowed to create a port '
|
||||
'for network %s.')
|
||||
% self.initial['network_id'])
|
||||
else:
|
||||
msg = (_('Failed to create a port for network %s')
|
||||
% self.initial['network_id'])
|
||||
redirect = reverse(self.failure_url,
|
||||
args=(self.initial['network_id'],))
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
@ -392,7 +392,7 @@ class NetworkPortTests(test.TestCase):
|
||||
args=[network.id])
|
||||
res = self.client.get(url)
|
||||
|
||||
self.assertTemplateUsed(res, 'project/networks/ports/create.html')
|
||||
self.assertTemplateUsed(res, views.WorkflowView.template_name)
|
||||
|
||||
@test.create_stubs({api.neutron: ('network_get',
|
||||
'is_extension_supported',
|
||||
@ -446,7 +446,6 @@ class NetworkPortTests(test.TestCase):
|
||||
**extension_kwargs) \
|
||||
.AndReturn(port)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'network_id': port.network_id,
|
||||
'network_name': network.name,
|
||||
'name': port.name,
|
||||
|
@ -16,15 +16,12 @@ from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import tabs
|
||||
from horizon.utils import memoized
|
||||
from horizon import workflows
|
||||
|
||||
from openstack_dashboard import api
|
||||
|
||||
from openstack_dashboard.dashboards.project.networks.ports \
|
||||
import forms as project_forms
|
||||
from openstack_dashboard.dashboards.project.networks.ports \
|
||||
import tables as project_tables
|
||||
from openstack_dashboard.dashboards.project.networks.ports \
|
||||
@ -38,19 +35,9 @@ STATUS_DICT = dict(project_tables.STATUS_DISPLAY_CHOICES)
|
||||
VNIC_TYPE_DICT = dict(api.neutron.VNIC_TYPES)
|
||||
|
||||
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = project_forms.CreatePort
|
||||
form_id = "create_port_form"
|
||||
modal_header = _("Create Port")
|
||||
submit_label = _("Create Port")
|
||||
submit_url = "horizon:project:networks:addport"
|
||||
page_title = _("Create Port")
|
||||
template_name = 'project/networks/ports/create.html'
|
||||
url = 'horizon:project:networks:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.url,
|
||||
args=(self.kwargs['network_id'],))
|
||||
class CreateView(workflows.WorkflowView):
|
||||
workflow_class = project_workflows.CreatePort
|
||||
failure_url = 'horizon:project:networks:detail'
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_network(self):
|
||||
@ -58,23 +45,16 @@ class CreateView(forms.ModalFormView):
|
||||
network_id = self.kwargs["network_id"]
|
||||
return api.neutron.network_get(self.request, network_id)
|
||||
except Exception:
|
||||
redirect = reverse(self.url,
|
||||
redirect = reverse(self.failure_url,
|
||||
args=(self.kwargs['network_id'],))
|
||||
msg = _("Unable to retrieve network.")
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CreateView, self).get_context_data(**kwargs)
|
||||
context['network'] = self.get_network()
|
||||
args = (self.kwargs['network_id'],)
|
||||
context['submit_url'] = reverse(self.submit_url, args=args)
|
||||
context['cancel_url'] = reverse(self.url, args=args)
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
network = self.get_network()
|
||||
return {"network_id": self.kwargs['network_id'],
|
||||
"network_name": network.name}
|
||||
"network_name": network.name,
|
||||
"target_tenant_id": network.tenant_id}
|
||||
|
||||
|
||||
class DetailView(tabs.TabbedTableView):
|
||||
|
@ -32,6 +32,215 @@ from openstack_dashboard.utils import filters
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreatePortInfoAction(workflows.Action):
|
||||
name = forms.CharField(max_length=255,
|
||||
label=_("Name"),
|
||||
required=False)
|
||||
admin_state = forms.BooleanField(label=_("Enable 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=_("Owner of the device attached to the port"),
|
||||
required=False)
|
||||
specify_ip = forms.ThemableChoiceField(
|
||||
label=_("Specify IP address or subnet"),
|
||||
help_text=_("To specify a subnet or a fixed IP, select any options."),
|
||||
required=False,
|
||||
choices=[('', _("Unspecified")),
|
||||
('subnet_id', _("Subnet")),
|
||||
('fixed_ip', _("Fixed IP Address"))],
|
||||
widget=forms.ThemableSelectWidget(attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'specify_ip',
|
||||
}))
|
||||
subnet_id = forms.ThemableChoiceField(
|
||||
label=_("Subnet"),
|
||||
required=False,
|
||||
widget=forms.ThemableSelectWidget(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'specify_ip',
|
||||
'data-specify_ip-subnet_id': _('Subnet'),
|
||||
}))
|
||||
fixed_ip = forms.IPField(
|
||||
label=_("Fixed IP Address"),
|
||||
required=False,
|
||||
help_text=_("Specify the subnet IP address for the new port"),
|
||||
version=forms.IPv4 | forms.IPv6,
|
||||
widget=forms.TextInput(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'specify_ip',
|
||||
'data-specify_ip-fixed_ip': _('Fixed IP Address'),
|
||||
}))
|
||||
mac_address = forms.MACAddressField(
|
||||
label=_("MAC Address"),
|
||||
required=False,
|
||||
help_text=_("Specify the MAC address for the new port"))
|
||||
mac_state = forms.BooleanField(
|
||||
label=_("MAC Learning State"), initial=False,
|
||||
required=False)
|
||||
port_security_enabled = forms.BooleanField(
|
||||
label=_("Port Security"),
|
||||
help_text=_("Enable anti-spoofing rules for the port"),
|
||||
initial=True,
|
||||
required=False)
|
||||
binding__vnic_type = forms.ThemableChoiceField(
|
||||
label=_("VNIC Type"),
|
||||
help_text=_("The VNIC type that is bound to the network port"),
|
||||
required=False)
|
||||
|
||||
def __init__(self, request, context, *args, **kwargs):
|
||||
super(CreatePortInfoAction, self).__init__(
|
||||
request, context, *args, **kwargs)
|
||||
|
||||
# prepare subnet choices and input area for each subnet
|
||||
subnet_choices = self._get_subnet_choices(context)
|
||||
if subnet_choices:
|
||||
subnet_choices.insert(0, ('', _("Select a subnet")))
|
||||
self.fields['subnet_id'].choices = subnet_choices
|
||||
else:
|
||||
self.fields['specify_ip'].widget = forms.HiddenInput()
|
||||
self.fields['subnet_id'].widget = forms.HiddenInput()
|
||||
self.fields['fixed_ip'].widget = forms.HiddenInput()
|
||||
|
||||
self._hide_field_if_not_supported(
|
||||
request, 'mac_state', 'mac-learning',
|
||||
_("Unable to retrieve MAC learning state"))
|
||||
self._hide_field_if_not_supported(
|
||||
request, 'port_security_enabled', 'port-security',
|
||||
_("Unable to retrieve port security state"))
|
||||
|
||||
self._populate_vnic_type_choices(request)
|
||||
|
||||
def _hide_field_if_not_supported(self, request, field, extension_alias,
|
||||
failure_message):
|
||||
is_supproted = False
|
||||
try:
|
||||
is_supproted = api.neutron.is_extension_supported(
|
||||
request, extension_alias)
|
||||
except Exception:
|
||||
exceptions.handle(self.request, failure_message)
|
||||
if not is_supproted:
|
||||
del self.fields[field]
|
||||
return is_supproted
|
||||
|
||||
def _populate_vnic_type_choices(self, request):
|
||||
neutron_settings = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {})
|
||||
supported_vnic_types = neutron_settings.get('supported_vnic_types',
|
||||
['*'])
|
||||
# When a list of VNIC types is empty, hide the corresponding field.
|
||||
if not supported_vnic_types:
|
||||
del self.fields['binding__vnic_type']
|
||||
return
|
||||
|
||||
binding_supported = self._hide_field_if_not_supported(
|
||||
request, 'binding__vnic_type', 'binding',
|
||||
_("Unable to verify the VNIC types extension in Neutron"))
|
||||
if not binding_supported:
|
||||
# binding__vnic_type field is already deleted, so return here
|
||||
return
|
||||
|
||||
if supported_vnic_types == ['*']:
|
||||
vnic_type_choices = api.neutron.VNIC_TYPES
|
||||
else:
|
||||
vnic_type_choices = [
|
||||
vnic_type for vnic_type in api.neutron.VNIC_TYPES
|
||||
if vnic_type[0] in supported_vnic_types
|
||||
]
|
||||
self.fields['binding__vnic_type'].choices = vnic_type_choices
|
||||
|
||||
def _get_subnet_choices(self, context):
|
||||
try:
|
||||
network_id = context['network_id']
|
||||
network = api.neutron.network_get(self.request, network_id)
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
# NOTE(amotoki): When a user cannot retrieve a subnet info,
|
||||
# subnet ID is stored in network.subnets field.
|
||||
# If so, we skip such subnet as subnet choices.
|
||||
# This happens usually for external networks.
|
||||
# TODO(amotoki): Ideally it is better to disable/hide
|
||||
# Create Port button in the port table, but as of Pike
|
||||
# the default neutron policy.json for "create_port" is empty
|
||||
# and there seems no appropriate policy. This is a dirty hack.
|
||||
return [(subnet.id, '%s %s' % (subnet.name_or_id, subnet.cidr))
|
||||
for subnet in network.subnets
|
||||
if isinstance(subnet, api.neutron.Subnet)]
|
||||
|
||||
class Meta(object):
|
||||
name = _("Info")
|
||||
slug = 'create_info'
|
||||
help_text_template = 'project/networks/ports/_create_port_help.html'
|
||||
|
||||
|
||||
class CreatePortInfo(workflows.Step):
|
||||
action_class = CreatePortInfoAction
|
||||
depends_on = ("network_id",)
|
||||
contributes = ["name", "admin_state", "device_id", "device_owner",
|
||||
"specify_ip", "subnet_id", "fixed_ip", "mac_address",
|
||||
"mac_state", "port_security_enabled", "binding__vnic_type"]
|
||||
|
||||
|
||||
class CreatePort(workflows.Workflow):
|
||||
slug = "create_port"
|
||||
name = _("Create Port")
|
||||
finalize_button_name = _("Create")
|
||||
success_message = _('Port %s was successfully created.')
|
||||
failure_message = _('Failed to create port "%s".')
|
||||
default_steps = (CreatePortInfo,)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse("horizon:project:networks:detail",
|
||||
args=(self.context['network_id'],))
|
||||
|
||||
def format_status_message(self, message):
|
||||
name = self.context['name'] or self.context.get('port_id', '')
|
||||
return message % name
|
||||
|
||||
def handle(self, request, context):
|
||||
try:
|
||||
params = self._construct_parameters(context)
|
||||
port = api.neutron.port_create(request, **params)
|
||||
self.context['port_id'] = port.id
|
||||
return True
|
||||
except Exception as e:
|
||||
LOG.info('Failed to create a port for network %(id)s: %(exc)s',
|
||||
{'id': context['network_id'], 'exc': e})
|
||||
|
||||
def _construct_parameters(self, context):
|
||||
params = {
|
||||
'network_id': context['network_id'],
|
||||
'admin_state_up': context['admin_state'],
|
||||
'name': context['name'],
|
||||
'device_id': context['device_id'],
|
||||
'device_owner': context['device_owner']
|
||||
}
|
||||
|
||||
if context.get('specify_ip') == 'subnet_id':
|
||||
if context.get('subnet_id'):
|
||||
params['fixed_ips'] = [{"subnet_id": context['subnet_id']}]
|
||||
elif context.get('specify_ip') == 'fixed_ip':
|
||||
if context.get('fixed_ip'):
|
||||
params['fixed_ips'] = [{"ip_address": context['fixed_ip']}]
|
||||
|
||||
if context.get('binding__vnic_type'):
|
||||
params['binding__vnic_type'] = context['binding__vnic_type']
|
||||
if context.get('mac_state'):
|
||||
params['mac_learning_enabled'] = context['mac_state']
|
||||
if 'port_security_enabled' in context:
|
||||
params['port_security_enabled'] = context['port_security_enabled']
|
||||
|
||||
# Send mac_address only when it is specified.
|
||||
if context['mac_address']:
|
||||
params['mac_address'] = context['mac_address']
|
||||
|
||||
return params
|
||||
|
||||
|
||||
class UpdatePortInfoAction(workflows.Action):
|
||||
name = forms.CharField(max_length=255,
|
||||
label=_("Name"),
|
||||
|
@ -1,11 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% blocktrans trimmed %} You can create a port for the network.
|
||||
If you specify device ID to be attached, the device specified will
|
||||
be attached to the port created.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% endblock %}
|
@ -0,0 +1,8 @@
|
||||
{% load i18n %}
|
||||
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% blocktrans trimmed %} You can create a port for the network.
|
||||
If you specify device ID to be attached, the device specified will
|
||||
be attached to the port created.
|
||||
{% endblocktrans %}
|
||||
</p>
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create Port" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include "project/networks/ports/_create.html" %}
|
||||
{% endblock %}
|
Loading…
Reference in New Issue
Block a user