Merge "Adds initial SRIOV creation/config support"

This commit is contained in:
Jenkins 2015-03-19 17:57:31 +00:00 committed by Gerrit Code Review
commit a174b4294d
15 changed files with 361 additions and 119 deletions

View File

@ -678,6 +678,7 @@ Default::
'enable_vpn': True,
'profile_support': None,
'supported_provider_types': ["*"],
'supported_vnic_types': ["*"],
'segmentation_id_range': {}
}
@ -803,6 +804,19 @@ be available to choose from.
Example: ``['local', 'flat', 'gre']``
``supported_vnic_types``:
.. versionadded:: 2015.1(Kilo)
Default ``['*']``
For use with the port binding extension. Use this to explicitly set which VNIC
types are supported; only those listed will be shown when creating or editing
a port. VNIC types include normal, direct and macvtap. By default all VNIC
types will be available to choose from.
Example ``['normal', 'direct']``
``segmentation_id_range``:
.. versionadded:: 2014.2(Juno)

View File

@ -88,9 +88,9 @@ class Network(NeutronAPIDictWrapper):
def __init__(self, apiresource):
apiresource['admin_state'] = \
'UP' if apiresource['admin_state_up'] else 'DOWN'
# Django cannot handle a key name with a colon, so remap another key
# Django cannot handle a key name with ':', so use '__'
for key in apiresource.keys():
if key.find(':'):
if ':' in key:
apiresource['__'.join(key.split(':'))] = apiresource[key]
super(Network, self).__init__(apiresource)
@ -112,6 +112,10 @@ class Port(NeutronAPIDictWrapper):
"""Wrapper for neutron ports."""
def __init__(self, apiresource):
# Django cannot handle a key name with ':', so use '__'
for key in apiresource.keys():
if ':' in key:
apiresource['__'.join(key.split(':'))] = apiresource[key]
apiresource['admin_state'] = \
'UP' if apiresource['admin_state_up'] else 'DOWN'
if 'mac_learning_enabled' in apiresource:
@ -729,6 +733,13 @@ def port_get(request, port_id, **params):
return Port(port)
def unescape_port_kwargs(**kwargs):
for key in kwargs:
if '__' in key:
kwargs[':'.join(key.split('__'))] = kwargs.pop(key)
return kwargs
def port_create(request, network_id, **kwargs):
"""Create a port on a specified network.
@ -743,6 +754,7 @@ def port_create(request, network_id, **kwargs):
# In the case policy profiles are being used, profile id is needed.
if 'policy_profile_id' in kwargs:
kwargs['n1kv:profile_id'] = kwargs.pop('policy_profile_id')
kwargs = unescape_port_kwargs(**kwargs)
body = {'port': {'network_id': network_id}}
if 'tenant_id' not in kwargs:
kwargs['tenant_id'] = request.user.project_id
@ -758,6 +770,7 @@ def port_delete(request, port_id):
def port_update(request, port_id, **kwargs):
LOG.debug("port_update(): portid=%s, kwargs=%s" % (port_id, kwargs))
kwargs = unescape_port_kwargs(**kwargs)
body = {'port': kwargs}
port = neutronclient(request).update_port(port_id, body=body).get('port')
return Port(port)

View File

@ -14,6 +14,7 @@
import logging
from django.conf import settings
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
@ -27,6 +28,8 @@ from openstack_dashboard.dashboards.project.networks.ports \
LOG = logging.getLogger(__name__)
VNIC_TYPES = [('normal', _('Normal')), ('direct', _('Direct')),
('macvtap', _('MacVTap'))]
class CreatePort(forms.SelfHandlingForm):
@ -49,9 +52,36 @@ class CreatePort(forms.SelfHandlingForm):
help_text=_("Device owner attached to the "
"port"),
required=False)
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 __init__(self, request, *args, **kwargs):
super(CreatePort, self).__init__(request, *args, **kwargs)
if api.neutron.is_extension_supported(request, 'binding'):
neutron_settings = getattr(settings,
'OPENSTACK_NEUTRON_NETWORK', {})
supported_vnic_types = neutron_settings.get(
'supported_vnic_types', ['*'])
if supported_vnic_types == ['*']:
vnic_type_choices = VNIC_TYPES
else:
vnic_type_choices = [
vnic_type for vnic_type in VNIC_TYPES
if vnic_type[0] in supported_vnic_types
]
self.fields['binding__vnic_type'] = forms.ChoiceField(
choices=vnic_type_choices,
label=_("Binding: VNIC Type"),
help_text=_("The VNIC type that is bound to the neutron port"),
required=False)
if api.neutron.is_extension_supported(request, 'mac-learning'):
self.fields['mac_state'] = forms.BooleanField(
label=_("MAC Learning State"), initial=False, required=False)
@ -78,7 +108,7 @@ class CreatePort(forms.SelfHandlingForm):
msg = _('Failed to create a port for network %s') \
% data['network_id']
LOG.info(msg)
redirect = reverse('horizon:admin:networks:detail',
redirect = reverse(self.failure_url,
args=(data['network_id'],))
exceptions.handle(request, msg, redirect=redirect)
@ -92,6 +122,13 @@ class UpdatePort(project_forms.UpdatePort):
help_text=_("Device owner attached to the "
"port"),
required=False)
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):
@ -99,13 +136,21 @@ class UpdatePort(project_forms.UpdatePort):
LOG.debug('params = %s' % data)
extension_kwargs = {}
data['admin_state'] = (data['admin_state'] == 'True')
if 'binding__vnic_type' in data:
extension_kwargs['binding__vnic_type'] = \
data['binding__vnic_type']
if 'mac_state' in data:
extension_kwargs['mac_learning_enabled'] = data['mac_state']
port = api.neutron.port_update(request, data['port_id'],
port = api.neutron.port_update(request,
data['port_id'],
name=data['name'],
admin_state_up=data['admin_state'],
device_id=data['device_id'],
device_owner=data['device_owner'],
binding__host_id=data
['binding__host_id'],
**extension_kwargs)
msg = _('Port %s was successfully updated.') % data['port_id']
LOG.debug(msg)

View File

@ -73,36 +73,14 @@ class CreatePort(tables.LinkAction):
return reverse(self.url, args=(network_id,))
class UpdatePort(policy.PolicyTargetMixin, tables.LinkAction):
name = "update"
verbose_name = _("Edit Port")
class UpdatePort(project_tables.UpdatePort):
url = "horizon:admin:networks:editport"
classes = ("ajax-modal",)
icon = "pencil"
policy_rules = (("network", "update_port"),)
def get_link_url(self, port):
network_id = self.table.kwargs['network_id']
return reverse(self.url, args=(network_id, port.id))
class PortsTable(tables.DataTable):
class PortsTable(project_tables.PortsTable):
name = tables.Column("name_or_id",
verbose_name=_("Name"),
link="horizon:admin:networks:ports:detail")
fixed_ips = tables.Column(
project_tables.get_fixed_ips, verbose_name=_("Fixed IPs"))
device_id = tables.Column(
project_tables.get_attached, verbose_name=_("Device Attached"))
status = tables.Column(
"status",
verbose_name=_("Status"),
display_choices=project_tables.STATUS_DISPLAY_CHOICES)
admin_state = tables.Column("admin_state",
verbose_name=_("Admin State"),
display_choices=project_tables.DISPLAY_CHOICES)
mac_state = tables.Column("mac_state", empty_value=api.neutron.OFF_STATE,
verbose_name=_("Mac Learning State"))
class Meta(object):
name = "ports"
@ -110,10 +88,3 @@ class PortsTable(tables.DataTable):
table_actions = (CreatePort, DeletePort)
row_actions = (UpdatePort, DeletePort,)
hidden_title = False
def __init__(self, request, data=None, needs_form_wrapper=None, **kwargs):
super(PortsTable, self).__init__(request, data=data,
needs_form_wrapper=needs_form_wrapper,
**kwargs)
if not api.neutron.is_extension_supported(request, 'mac-learning'):
del self.columns['mac_state']

View File

@ -12,31 +12,13 @@
# License for the specific language governing permissions and limitations
# under the License.
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from openstack_dashboard import api
from openstack_dashboard.dashboards.project.networks.ports \
import tabs as project_tabs
class OverviewTab(tabs.Tab):
name = _("Overview")
slug = "overview"
template_name = "project/networks/ports/_detail_overview.html"
def get_context_data(self, request):
port_id = self.tab_group.kwargs['port_id']
try:
port = api.neutron.port_get(self.request, port_id)
except Exception:
redirect = reverse('horizon:admin:networks:index')
msg = _('Unable to retrieve port details.')
exceptions.handle(request, msg, redirect=redirect)
return {'port': port}
class OverviewTab(project_tabs.OverviewTab):
template_name = "admin/networks/ports/_detail_overview.html"
class PortDetailTabs(tabs.TabGroup):
slug = "port_details"
class PortDetailTabs(project_tabs.PortDetailTabs):
tabs = (OverviewTab,)

View File

@ -15,7 +15,7 @@
from django.conf.urls import patterns
from django.conf.urls import url
from openstack_dashboard.dashboards.project.networks.ports import views
from openstack_dashboard.dashboards.admin.networks.ports import views
PORTS = r'^(?P<port_id>[^/]+)/%s$'
VIEW_MOD = 'openstack_dashboard.dashboards.admin.networks.ports.views'

View File

@ -20,26 +20,29 @@ 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 \
import tabs as ports_tabs
from openstack_dashboard.dashboards.project.networks.ports \
import views as project_views
from openstack_dashboard.dashboards.admin.networks.ports \
import forms as project_forms
class CreateView(forms.ModalFormView):
form_class = project_forms.CreatePort
form_class = ports_forms.CreatePort
form_id = "create_port_form"
modal_header = _("Create Port")
template_name = 'admin/networks/ports/create.html'
submit_label = _("Create Port")
submit_url = "horizon:admin:networks:addport"
success_url = 'horizon:admin:networks:detail'
failure_url = 'horizon:admin:networks:detail'
page_title = _("Create Port")
template_name = 'admin/networks/ports/create.html'
url = 'horizon:admin:networks:detail'
def get_success_url(self):
return reverse(self.success_url,
return reverse(self.url,
args=(self.kwargs['network_id'],))
@memoized.memoized_method
@ -48,7 +51,7 @@ class CreateView(forms.ModalFormView):
network_id = self.kwargs["network_id"]
return api.neutron.network_get(self.request, network_id)
except Exception:
redirect = reverse(self.failure_url,
redirect = reverse(self.url,
args=(self.kwargs['network_id'],))
msg = _("Unable to retrieve network.")
exceptions.handle(self.request, msg, redirect=redirect)
@ -66,9 +69,32 @@ class CreateView(forms.ModalFormView):
"network_name": network.name}
class DetailView(project_views.DetailView):
tab_group_class = ports_tabs.PortDetailTabs
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
port = context["port"]
table = ports_tables.PortsTable(self.request,
network_id=port.network_id)
context["url"] = reverse('horizon:admin:networks:index')
context["actions"] = table.render_row_actions(port)
return context
@staticmethod
def get_redirect_url():
return reverse('horizon:admin:networks:index')
class UpdateView(project_views.UpdateView):
form_class = project_forms.UpdatePort
form_class = ports_forms.UpdatePort
template_name = 'admin/networks/ports/update.html'
context_object_name = 'port'
submit_url = "horizon:admin:networks:editport"
success_url = 'horizon:admin:networks:detail'
def get_initial(self):
initial = super(UpdateView, self).get_initial()
port = self._get_object()
initial['binding__host_id'] = port['binding__host_id']
return initial

View File

@ -0,0 +1,73 @@
{% load i18n sizeformat %}
{% load url from future %}
<div class="detail">
<dl class="dl-horizontal">
<dt>{% trans "Name" %}</dt>
<dd>{{ port.name|default:_("None") }}</dd>
<dt>{% trans "ID" %}</dt>
<dd>{{ port.id|default:_("None") }}</dd>
{% url 'horizon:project:networks:detail' port.network_id as network_url %}
<dt>{% trans "Network ID" %}</dt>
<dd><a href="{{ network_url }}">{{ port.network_id|default:_("None") }}</a></dd>
<dt>{% trans "Project ID" %}</dt>
<dd>{{ port.tenant_id|default:_("-") }}</dd>
<dt>{% trans "MAC Address" %}</dt>
<dd>{{ port.mac_address|default:_("None") }}</dd>
<dt>{% trans "Status" %}</dt>
<dd>{{ port.status_label|default:_("None") }}</dd>
<dt>{% trans "Admin State" %}</dt>
<dd>{{ port.admin_state_label|default:_("None") }}</dd>
{% if port.mac_state %}
<dt>{% trans "MAC Learning State" %}</dt>
<dd>{% trans "On" %}</dd>
{% endif %}
<h4>{% trans "Fixed IP" %}</h4>
<hr class="header_rule">
{% if port.fixed_ips.items|length > 1 %}
{% for ip in port.fixed_ips %}
<dt>{% trans "IP Address" %}</dt>
<dd>{{ ip.ip_address }}</dd>
{% url 'horizon:project:networks:subnets:detail' ip.subnet_id as subnet_url %}
<dt>{% trans "Subnet ID" %}</dt>
<dd><a href="{{ subnet_url }}">{{ ip.subnet_id }}</a></dd>
{% endfor %}
{% else %}
<dd>{% trans "None" %}</dd>
{% endif %}
<h4>{% trans "Attached Device" %}</h4>
<hr class="header_rule">
{% if port.device_id|length > 1 or port.device_owner %}
<dt>{% trans "Device Owner" %}</dt>
<dd>{{ port.device_owner|default:_("None") }}</dd>
<dt>{% trans "Device ID" %}</dt>
<dd>{{ port.device_id|default:_("None") }}</dd>
{% else %}
<dd>{% trans "No attached device" %}</dd>
{% endif %}
<h4>{% trans "Binding" %}</h4>
<hr class="header_rule">
<dt>{% trans "Host" %}</dt>
<dd>{{ port.binding__host_id|default:_("None") }}</dd>
<dt>{% trans "Profile" %}</dt>
<dd>{{ port.binding__profile|default:_("None") }}</dd>
<dt>{% trans "VIF Type" %}</dt>
<dd>{{ port.binding__vif_type|replace_underscores }}</dd>
<dt>{% trans "VIF Details" %}</dt>
{% if port.binding__vif_details.items %}
<dd>
<ul>
{% for key,value in port.binding__vif_details.items %}
<li><b>{{ key }}</b> {{ value }}</li>
{% endfor %}
</ul>
</dd>
{% else %}
<dd>{% trans "None" %}</dd>
{% endif %}
{% if port.binding__vnic_type %}
<dt>{% trans "VNIC Type" %}</dt>
<dd>{{ port.binding__vnic_type }}</dd>
{% endif %}
</dl>
</div>

View File

@ -975,7 +975,9 @@ class NetworkPortTests(test.BaseAdminViewTests):
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\
.AndReturn(mac_learning)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\
.AndReturn(mac_learning)
self.mox.ReplayAll()
res = self.client.get(reverse('horizon:admin:networks:ports:detail',
@ -997,7 +999,7 @@ class NetworkPortTests(test.BaseAdminViewTests):
# admin DetailView is shared with userpanel one, so
# redirection URL on error is userpanel index.
redir_url = reverse('horizon:project:networks:index')
redir_url = reverse('horizon:admin:networks:index')
self.assertRedirectsNoFollow(res, redir_url)
@test.create_stubs({api.neutron: ('network_get',
@ -1010,11 +1012,14 @@ class NetworkPortTests(test.BaseAdminViewTests):
def test_port_create_get_with_mac_learning(self):
self._test_port_create_get(mac_learning=True)
def _test_port_create_get(self, mac_learning=False):
def _test_port_create_get(self, mac_learning=False, binding=False):
network = self.networks.first()
api.neutron.network_get(IsA(http.HttpRequest),
network.id)\
.AndReturn(self.networks.first())
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'binding')\
.AndReturn(binding)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\
.AndReturn(mac_learning)
@ -1036,9 +1041,9 @@ class NetworkPortTests(test.BaseAdminViewTests):
'is_extension_supported',
'port_create',)})
def test_port_create_post_with_mac_learning(self):
self._test_port_create_post(mac_learning=True)
self._test_port_create_post(mac_learning=True, binding=False)
def _test_port_create_post(self, mac_learning=False):
def _test_port_create_post(self, mac_learning=False, binding=False):
network = self.networks.first()
port = self.ports.first()
api.neutron.network_get(IsA(http.HttpRequest),
@ -1047,10 +1052,16 @@ class NetworkPortTests(test.BaseAdminViewTests):
api.neutron.network_get(IsA(http.HttpRequest),
network.id)\
.AndReturn(self.networks.first())
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'binding')\
.AndReturn(binding)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\
.AndReturn(mac_learning)
extension_kwargs = {}
if binding:
extension_kwargs['binding__vnic_type'] = \
port.binding__vnic_type
if mac_learning:
extension_kwargs['mac_learning_enabled'] = True
api.neutron.port_create(IsA(http.HttpRequest),
@ -1060,6 +1071,7 @@ class NetworkPortTests(test.BaseAdminViewTests):
admin_state_up=port.admin_state_up,
device_id=port.device_id,
device_owner=port.device_owner,
binding__host_id=port.binding__host_id,
**extension_kwargs)\
.AndReturn(port)
self.mox.ReplayAll()
@ -1069,7 +1081,10 @@ class NetworkPortTests(test.BaseAdminViewTests):
'name': port.name,
'admin_state': port.admin_state_up,
'device_id': port.device_id,
'device_owner': port.device_owner}
'device_owner': port.device_owner,
'binding__host_id': port.binding__host_id}
if binding:
form_data['binding__vnic_type'] = port.binding__vnic_type
if mac_learning:
form_data['mac_state'] = True
url = reverse('horizon:admin:networks:addport',
@ -1093,7 +1108,8 @@ class NetworkPortTests(test.BaseAdminViewTests):
def test_port_create_post_exception_with_mac_learning(self):
self._test_port_create_post_exception(mac_learning=True)
def _test_port_create_post_exception(self, mac_learning=False):
def _test_port_create_post_exception(self, mac_learning=False,
binding=False):
network = self.networks.first()
port = self.ports.first()
api.neutron.network_get(IsA(http.HttpRequest),
@ -1102,10 +1118,15 @@ class NetworkPortTests(test.BaseAdminViewTests):
api.neutron.network_get(IsA(http.HttpRequest),
network.id)\
.AndReturn(self.networks.first())
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'binding')\
.AndReturn(binding)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\
.AndReturn(mac_learning)
extension_kwargs = {}
if binding:
extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
if mac_learning:
extension_kwargs['mac_learning_enabled'] = True
api.neutron.port_create(IsA(http.HttpRequest),
@ -1115,6 +1136,7 @@ class NetworkPortTests(test.BaseAdminViewTests):
admin_state_up=port.admin_state_up,
device_id=port.device_id,
device_owner=port.device_owner,
binding__host_id=port.binding__host_id,
**extension_kwargs)\
.AndRaise(self.exceptions.neutron)
self.mox.ReplayAll()
@ -1125,7 +1147,10 @@ class NetworkPortTests(test.BaseAdminViewTests):
'admin_state': port.admin_state_up,
'mac_state': True,
'device_id': port.device_id,
'device_owner': port.device_owner}
'device_owner': port.device_owner,
'binding__host_id': port.binding__host_id}
if binding:
form_data['binding__vnic_type'] = port.binding__vnic_type
if mac_learning:
form_data['mac_learning_enabled'] = True
url = reverse('horizon:admin:networks:addport',
@ -1147,11 +1172,14 @@ class NetworkPortTests(test.BaseAdminViewTests):
def test_port_update_get_with_mac_learning(self):
self._test_port_update_get(mac_learning=True)
def _test_port_update_get(self, mac_learning=False):
def _test_port_update_get(self, mac_learning=False, binding=False):
port = self.ports.first()
api.neutron.port_get(IsA(http.HttpRequest),
port.id)\
.AndReturn(port)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'binding')\
.AndReturn(binding)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\
.AndReturn(mac_learning)
@ -1175,14 +1203,19 @@ class NetworkPortTests(test.BaseAdminViewTests):
def test_port_update_post_with_mac_learning(self):
self._test_port_update_post(mac_learning=True)
def _test_port_update_post(self, mac_learning=False):
def _test_port_update_post(self, mac_learning=False, binding=False):
port = self.ports.first()
api.neutron.port_get(IsA(http.HttpRequest), port.id)\
.AndReturn(port)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'binding')\
.AndReturn(binding)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\
.AndReturn(mac_learning)
extension_kwargs = {}
if binding:
extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
if mac_learning:
extension_kwargs['mac_learning_enabled'] = True
api.neutron.port_update(IsA(http.HttpRequest), port.id,
@ -1190,6 +1223,7 @@ class NetworkPortTests(test.BaseAdminViewTests):
admin_state_up=port.admin_state_up,
device_id=port.device_id,
device_owner=port.device_owner,
binding__host_id=port.binding__host_id,
**extension_kwargs)\
.AndReturn(port)
self.mox.ReplayAll()
@ -1199,7 +1233,10 @@ class NetworkPortTests(test.BaseAdminViewTests):
'name': port.name,
'admin_state': port.admin_state_up,
'device_id': port.device_id,
'device_owner': port.device_owner}
'device_owner': port.device_owner,
'binding__host_id': port.binding__host_id}
if binding:
form_data['binding__vnic_type'] = port.binding__vnic_type
if mac_learning:
form_data['mac_state'] = True
url = reverse('horizon:admin:networks:editport',
@ -1220,16 +1257,22 @@ class NetworkPortTests(test.BaseAdminViewTests):
'is_extension_supported',
'port_update')})
def test_port_update_post_exception_with_mac_learning(self):
self._test_port_update_post_exception(mac_learning=True)
self._test_port_update_post_exception(mac_learning=True, binding=False)
def _test_port_update_post_exception(self, mac_learning=False):
def _test_port_update_post_exception(self, mac_learning=False,
binding=False):
port = self.ports.first()
api.neutron.port_get(IsA(http.HttpRequest), port.id)\
.AndReturn(port)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'binding')\
.AndReturn(binding)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\
.AndReturn(mac_learning)
extension_kwargs = {}
if binding:
extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
if mac_learning:
extension_kwargs['mac_learning_enabled'] = True
api.neutron.port_update(IsA(http.HttpRequest), port.id,
@ -1237,6 +1280,7 @@ class NetworkPortTests(test.BaseAdminViewTests):
admin_state_up=port.admin_state_up,
device_id=port.device_id,
device_owner=port.device_owner,
binding__host_id=port.binding__host_id,
**extension_kwargs)\
.AndRaise(self.exceptions.neutron)
self.mox.ReplayAll()
@ -1246,7 +1290,10 @@ class NetworkPortTests(test.BaseAdminViewTests):
'name': port.name,
'admin_state': port.admin_state_up,
'device_id': port.device_id,
'device_owner': port.device_owner}
'device_owner': port.device_owner,
'binding__host_id': port.binding__host_id}
if binding:
form_data['binding__vnic_type'] = port.binding__vnic_type
if mac_learning:
form_data['mac_state'] = True
url = reverse('horizon:admin:networks:editport',

View File

@ -14,6 +14,7 @@
import logging
from django.conf import settings
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
@ -25,6 +26,8 @@ from openstack_dashboard import api
LOG = logging.getLogger(__name__)
VNIC_TYPES = [('normal', _('Normal')), ('direct', _('Direct')),
('macvtap', _('MacVTap'))]
class UpdatePort(forms.SelfHandlingForm):
@ -42,18 +45,42 @@ class UpdatePort(forms.SelfHandlingForm):
def __init__(self, request, *args, **kwargs):
super(UpdatePort, self).__init__(request, *args, **kwargs)
if api.neutron.is_extension_supported(request, 'binding'):
neutron_settings = getattr(settings,
'OPENSTACK_NEUTRON_NETWORK', {})
supported_vnic_types = neutron_settings.get(
'supported_vnic_types', ['*'])
if supported_vnic_types == ['*']:
vnic_type_choices = VNIC_TYPES
else:
vnic_type_choices = [
vnic_type for vnic_type in VNIC_TYPES
if vnic_type[0] in supported_vnic_types
]
self.fields['binding__vnic_type'] = forms.ChoiceField(
choices=vnic_type_choices,
label=_("Binding: VNIC Type"),
help_text=_("The VNIC type that is bound to the neutron port"),
required=False)
if api.neutron.is_extension_supported(request, 'mac-learning'):
self.fields['mac_state'] = forms.BooleanField(
label=_("Mac Learning State"), required=False)
label=_("MAC Learning State"), initial=False, required=False)
def handle(self, request, data):
data['admin_state'] = (data['admin_state'] == 'True')
try:
LOG.debug('params = %s' % data)
extension_kwargs = {}
if 'binding__vnic_type' in data:
extension_kwargs['binding__vnic_type'] = \
data['binding__vnic_type']
if 'mac_state' in data:
extension_kwargs['mac_learning_enabled'] = data['mac_state']
port = api.neutron.port_update(request, data['port_id'],
port = api.neutron.port_update(request,
data['port_id'],
name=data['name'],
admin_state_up=data['admin_state'],
**extension_kwargs)

View File

@ -100,8 +100,7 @@ class UpdateView(forms.ModalFormView):
try:
return api.neutron.port_get(self.request, port_id)
except Exception:
redirect = reverse("horizon:project:networks:detail",
args=(self.kwargs['network_id'],))
redirect = self.get_success_url()
msg = _('Unable to retrieve port details')
exceptions.handle(self.request, msg, redirect=redirect)
@ -120,9 +119,9 @@ class UpdateView(forms.ModalFormView):
'network_id': port['network_id'],
'tenant_id': port['tenant_id'],
'name': port['name'],
'admin_state': port['admin_state_up'],
'device_id': port['device_id'],
'device_owner': port['device_owner']}
'admin_state': port['admin_state_up']}
if port['binding__vnic_type']:
initial['binding__vnic_type'] = port['binding__vnic_type']
try:
initial['mac_state'] = port['mac_learning_enabled']
except Exception:

View File

@ -1,11 +1,7 @@
{% load i18n sizeformat %}
{% load url from future %}
<h3>{% trans "Port Overview" %}</h3>
<div class="info row detail">
<h4>{% trans "Port" %}</h4>
<hr class="header_rule">
<div class="detail">
<dl class="dl-horizontal">
<dt>{% trans "Name" %}</dt>
<dd>{{ port.name|default:_("None") }}</dd>
@ -16,18 +12,7 @@
<dd><a href="{{ network_url }}">{{ port.network_id|default:_("None") }}</a></dd>
<dt>{% trans "Project ID" %}</dt>
<dd>{{ port.tenant_id|default:_("-") }}</dd>
<dt>{% trans "Fixed IP" %}</dt>
<dd>
{% if port.fixed_ips.items|length > 1 %}
{% for ip in port.fixed_ips %}
<b>{% trans "IP address:" %}</b> {{ ip.ip_address }},
<b>{% trans "Subnet ID" %}</b> {{ ip.subnet_id }}<br>
{% endfor %}
{% else %}
{% trans "None" %}
{% endif %}
</dd>
<dt>{% trans "Mac Address" %}</dt>
<dt>{% trans "MAC Address" %}</dt>
<dd>{{ port.mac_address|default:_("None") }}</dd>
<dt>{% trans "Status" %}</dt>
<dd>{{ port.status_label|default:_("None") }}</dd>
@ -37,12 +22,34 @@
<dt>{% trans "MAC Learning State" %}</dt>
<dd>{{ port.mac_state }}</dd>
{% endif %}
<dt>{% trans "Attached Device" %}</dt>
<h4>{% trans "Fixed IP" %}</h4>
<hr class="header_rule">
{% if port.fixed_ips.items|length > 1 %}
{% for ip in port.fixed_ips %}
<dt>{% trans "IP Address" %}</dt>
<dd>{{ ip.ip_address }}</dd>
{% url 'horizon:project:networks:subnets:detail' ip.subnet_id as subnet_url %}
<dt>{% trans "Subnet ID" %}</dt>
<dd><a href="{{ subnet_url }}">{{ ip.subnet_id }}</a></dd>
{% endfor %}
{% else %}
<dd>{% trans "None" %}</dd>
{% endif %}
<h4>{% trans "Attached Device" %}</h4>
<hr class="header_rule">
{% if port.device_id|length > 1 or port.device_owner %}
<dd><b>{% trans "Device Owner" %}</b>: {{ port.device_owner|default:_("None") }}</dd>
<dd><b>{% trans "Device ID" %}</b>: {{ port.device_id|default:_("-") }}</dd>
<dt>{% trans "Device Owner" %}</dt>
<dd>{{ port.device_owner|default:_("None") }}</dd>
<dt>{% trans "Device ID" %}</dt>
<dd>{{ port.device_id|default:_("None") }}</dd>
{% else %}
<dd>{% trans "No attached device" %}</dd>
{% endif %}
<h4>{% trans "Binding" %}</h4>
<hr class="header_rule">
{% if port.binding__vnic_type %}
<dt>{% trans "VNIC Type" %}</dt>
<dd>{{ port.binding__vnic_type }}</dd>
{% endif %}
</dl>
</div>

View File

@ -1691,11 +1691,14 @@ class NetworkPortTests(test.TestCase):
def test_port_update_get_with_mac_learning(self):
self._test_port_update_get(mac_learning=True)
def _test_port_update_get(self, mac_learning=False):
def _test_port_update_get(self, mac_learning=False, binding=False):
port = self.ports.first()
api.neutron.port_get(IsA(http.HttpRequest),
port.id)\
.AndReturn(port)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'binding')\
.AndReturn(binding)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\
.AndReturn(mac_learning)
@ -1719,14 +1722,19 @@ class NetworkPortTests(test.TestCase):
def test_port_update_post_with_mac_learning(self):
self._test_port_update_post(mac_learning=True)
def _test_port_update_post(self, mac_learning=False):
def _test_port_update_post(self, mac_learning=False, binding=False):
port = self.ports.first()
api.neutron.port_get(IsA(http.HttpRequest), port.id)\
.AndReturn(port)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'binding')\
.AndReturn(binding)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\
.AndReturn(mac_learning)
extension_kwargs = {}
if binding:
extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
if mac_learning:
extension_kwargs['mac_learning_enabled'] = True
api.neutron.port_update(IsA(http.HttpRequest), port.id,
@ -1740,6 +1748,8 @@ class NetworkPortTests(test.TestCase):
'port_id': port.id,
'name': port.name,
'admin_state': port.admin_state_up}
if binding:
form_data['binding__vnic_type'] = port.binding__vnic_type
if mac_learning:
form_data['mac_state'] = True
url = reverse('horizon:project:networks:editport',
@ -1762,14 +1772,21 @@ class NetworkPortTests(test.TestCase):
def test_port_update_post_exception_with_mac_learning(self):
self._test_port_update_post_exception(mac_learning=True)
def _test_port_update_post_exception(self, mac_learning=False):
def _test_port_update_post_exception(self, mac_learning=False,
binding=False):
port = self.ports.first()
api.neutron.port_get(IsA(http.HttpRequest), port.id)\
.AndReturn(port)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'binding')\
.AndReturn(binding)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\
.AndReturn(mac_learning)
extension_kwargs = {}
if binding:
extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
if mac_learning:
extension_kwargs['mac_learning_enabled'] = True
api.neutron.port_update(IsA(http.HttpRequest), port.id,
@ -1783,6 +1800,8 @@ class NetworkPortTests(test.TestCase):
'port_id': port.id,
'name': port.name,
'admin_state': port.admin_state_up}
if binding:
form_data['binding__vnic_type'] = port.binding__vnic_type
if mac_learning:
form_data['mac_state'] = True
url = reverse('horizon:project:networks:editport',

View File

@ -216,6 +216,12 @@ OPENSTACK_NEUTRON_NETWORK = {
# in this list will be available to choose from when creating a network.
# Network types include local, flat, vlan, gre, and vxlan.
'supported_provider_types': ['*'],
# Set which VNIC types are supported for port binding. Only the VNIC
# types in this list will be available to choose from when creating a
# port.
# VNIC types include 'normal', 'macvtap' and 'direct'.
'supported_vnic_types': ['*']
}
# The OPENSTACK_IMAGE_BACKEND settings can be used to customize features

View File

@ -161,7 +161,10 @@ def data(TEST):
'name': '',
'network_id': network_dict['id'],
'status': 'ACTIVE',
'tenant_id': network_dict['tenant_id']}
'tenant_id': network_dict['tenant_id'],
'binding:vnic_type': 'normal',
'binding:host_id': 'host'}
TEST.api_ports.add(port_dict)
TEST.ports.add(neutron.Port(port_dict))
@ -175,7 +178,9 @@ def data(TEST):
'name': '',
'network_id': network_dict['id'],
'status': 'ACTIVE',
'tenant_id': network_dict['tenant_id']}
'tenant_id': network_dict['tenant_id'],
'binding:vnic_type': 'normal',
'binding:host_id': 'host'}
TEST.api_ports.add(port_dict)
TEST.ports.add(neutron.Port(port_dict))
assoc_port = port_dict
@ -190,7 +195,9 @@ def data(TEST):
'name': '',
'network_id': network_dict['id'],
'status': 'ACTIVE',
'tenant_id': network_dict['tenant_id']}
'tenant_id': network_dict['tenant_id'],
'binding:vnic_type': 'normal',
'binding:host_id': 'host'}
TEST.api_ports.add(port_dict)
TEST.ports.add(neutron.Port(port_dict))
@ -238,7 +245,9 @@ def data(TEST):
'name': '',
'network_id': network_dict['id'],
'status': 'ACTIVE',
'tenant_id': network_dict['tenant_id']}
'tenant_id': network_dict['tenant_id'],
'binding:vnic_type': 'normal',
'binding:host_id': 'host'}
TEST.api_ports.add(port_dict)
TEST.ports.add(neutron.Port(port_dict))
@ -350,7 +359,9 @@ def data(TEST):
'name': '',
'network_id': TEST.networks.get(name="ext_net")['id'],
'status': 'ACTIVE',
'tenant_id': '1'}
'tenant_id': '1',
'binding:vnic_type': 'normal',
'binding:host_id': 'host'}
TEST.api_ports.add(port_dict)
TEST.ports.add(neutron.Port(port_dict))
@ -1142,6 +1153,8 @@ def data(TEST):
'name': 'port5',
'network_id': TEST.networks.get(name="net4")['id'],
'status': 'ACTIVE',
'tenant_id': TEST.networks.get(name="net4")['tenant_id']}
'tenant_id': TEST.networks.get(name="net4")['tenant_id'],
'binding:vnic_type': 'normal',
'binding:host_id': 'host'}
TEST.api_ports.add(port_dict)
TEST.ports.add(neutron.Port(port_dict))