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:
wei.ying 2018-02-02 17:35:34 +08:00
parent 17b2c25d50
commit 88d78a489b
13 changed files with 265 additions and 427 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create Port" %}{% endblock %}
{% block main %}
{% include "admin/networks/ports/_create.html" %}
{% endblock %}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create Port" %}{% endblock %}
{% block main %}
{% include "project/networks/ports/_create.html" %}
{% endblock %}