diff --git a/openstack_dashboard/dashboards/admin/networks/ports/forms.py b/openstack_dashboard/dashboards/admin/networks/ports/forms.py deleted file mode 100644 index f1daea4c3e..0000000000 --- a/openstack_dashboard/dashboards/admin/networks/ports/forms.py +++ /dev/null @@ -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) diff --git a/openstack_dashboard/dashboards/admin/networks/ports/tests.py b/openstack_dashboard/dashboards/admin/networks/ports/tests.py index 3c8d082f27..6789fa2a62 100644 --- a/openstack_dashboard/dashboards/admin/networks/ports/tests.py +++ b/openstack_dashboard/dashboards/admin/networks/ports/tests.py @@ -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()) diff --git a/openstack_dashboard/dashboards/admin/networks/ports/views.py b/openstack_dashboard/dashboards/admin/networks/ports/views.py index 821931c76d..21a81bcbc5 100644 --- a/openstack_dashboard/dashboards/admin/networks/ports/views.py +++ b/openstack_dashboard/dashboards/admin/networks/ports/views.py @@ -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): diff --git a/openstack_dashboard/dashboards/admin/networks/ports/workflows.py b/openstack_dashboard/dashboards/admin/networks/ports/workflows.py index dd70738a16..70beccd971 100644 --- a/openstack_dashboard/dashboards/admin/networks/ports/workflows.py +++ b/openstack_dashboard/dashboards/admin/networks/ports/workflows.py @@ -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"), diff --git a/openstack_dashboard/dashboards/admin/networks/templates/networks/ports/_create.html b/openstack_dashboard/dashboards/admin/networks/templates/networks/ports/_create.html deleted file mode 100644 index 3260ae88a7..0000000000 --- a/openstack_dashboard/dashboards/admin/networks/templates/networks/ports/_create.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "horizon/common/_modal_form.html" %} -{% load i18n %} - -{% block modal-body-right %} -

{% trans "Description:" %}

-

{% 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."%}

-{% endblock %} diff --git a/openstack_dashboard/dashboards/admin/networks/templates/networks/ports/create.html b/openstack_dashboard/dashboards/admin/networks/templates/networks/ports/create.html deleted file mode 100644 index 7dbc4e7c78..0000000000 --- a/openstack_dashboard/dashboards/admin/networks/templates/networks/ports/create.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% block title %}{% trans "Create Port" %}{% endblock %} - -{% block main %} - {% include "admin/networks/ports/_create.html" %} -{% endblock %} diff --git a/openstack_dashboard/dashboards/project/networks/ports/forms.py b/openstack_dashboard/dashboards/project/networks/ports/forms.py deleted file mode 100644 index e253e107cf..0000000000 --- a/openstack_dashboard/dashboards/project/networks/ports/forms.py +++ /dev/null @@ -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) diff --git a/openstack_dashboard/dashboards/project/networks/ports/tests.py b/openstack_dashboard/dashboards/project/networks/ports/tests.py index 8806db3ff0..06c764759b 100644 --- a/openstack_dashboard/dashboards/project/networks/ports/tests.py +++ b/openstack_dashboard/dashboards/project/networks/ports/tests.py @@ -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, diff --git a/openstack_dashboard/dashboards/project/networks/ports/views.py b/openstack_dashboard/dashboards/project/networks/ports/views.py index 92943fe73b..d41d8a9fdc 100644 --- a/openstack_dashboard/dashboards/project/networks/ports/views.py +++ b/openstack_dashboard/dashboards/project/networks/ports/views.py @@ -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): diff --git a/openstack_dashboard/dashboards/project/networks/ports/workflows.py b/openstack_dashboard/dashboards/project/networks/ports/workflows.py index 9d47e2c269..416bad940c 100644 --- a/openstack_dashboard/dashboards/project/networks/ports/workflows.py +++ b/openstack_dashboard/dashboards/project/networks/ports/workflows.py @@ -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"), diff --git a/openstack_dashboard/dashboards/project/networks/templates/networks/ports/_create.html b/openstack_dashboard/dashboards/project/networks/templates/networks/ports/_create.html deleted file mode 100644 index e0d239051c..0000000000 --- a/openstack_dashboard/dashboards/project/networks/templates/networks/ports/_create.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends "horizon/common/_modal_form.html" %} -{% load i18n %} - -{% block modal-body-right %} -

{% trans "Description:" %}

-

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

-{% endblock %} diff --git a/openstack_dashboard/dashboards/project/networks/templates/networks/ports/_create_port_help.html b/openstack_dashboard/dashboards/project/networks/templates/networks/ports/_create_port_help.html new file mode 100644 index 0000000000..ac600c4d4e --- /dev/null +++ b/openstack_dashboard/dashboards/project/networks/templates/networks/ports/_create_port_help.html @@ -0,0 +1,8 @@ +{% load i18n %} + +

{% trans "Description:" %}

+

{% 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 %} +

diff --git a/openstack_dashboard/dashboards/project/networks/templates/networks/ports/create.html b/openstack_dashboard/dashboards/project/networks/templates/networks/ports/create.html deleted file mode 100644 index 922625d1aa..0000000000 --- a/openstack_dashboard/dashboards/project/networks/templates/networks/ports/create.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% block title %}{% trans "Create Port" %}{% endblock %} - -{% block main %} - {% include "project/networks/ports/_create.html" %} -{% endblock %}