Merge "Refactor the create port form to workflow"
This commit is contained in:
commit
d14439e0d8
|
@ -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())
|
||||
|
@ -205,14 +199,8 @@ class NetworkPortTests(test.BaseAdminViewTests):
|
|||
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())
|
||||
network.id) \
|
||||
.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