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, 'enable_vpn': True,
'profile_support': None, 'profile_support': None,
'supported_provider_types': ["*"], 'supported_provider_types': ["*"],
'supported_vnic_types': ["*"],
'segmentation_id_range': {} 'segmentation_id_range': {}
} }
@ -803,6 +804,19 @@ be available to choose from.
Example: ``['local', 'flat', 'gre']`` 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``: ``segmentation_id_range``:
.. versionadded:: 2014.2(Juno) .. versionadded:: 2014.2(Juno)

View File

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

View File

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

View File

@ -73,36 +73,14 @@ class CreatePort(tables.LinkAction):
return reverse(self.url, args=(network_id,)) return reverse(self.url, args=(network_id,))
class UpdatePort(policy.PolicyTargetMixin, tables.LinkAction): class UpdatePort(project_tables.UpdatePort):
name = "update"
verbose_name = _("Edit Port")
url = "horizon:admin:networks:editport" 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", name = tables.Column("name_or_id",
verbose_name=_("Name"), verbose_name=_("Name"),
link="horizon:admin:networks:ports:detail") 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): class Meta(object):
name = "ports" name = "ports"
@ -110,10 +88,3 @@ class PortsTable(tables.DataTable):
table_actions = (CreatePort, DeletePort) table_actions = (CreatePort, DeletePort)
row_actions = (UpdatePort, DeletePort,) row_actions = (UpdatePort, DeletePort,)
hidden_title = False 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 # License for the specific language governing permissions and limitations
# under the License. # under the License.
from django.core.urlresolvers import reverse from openstack_dashboard.dashboards.project.networks.ports \
from django.utils.translation import ugettext_lazy as _ import tabs as project_tabs
from horizon import exceptions
from horizon import tabs
from openstack_dashboard import api
class OverviewTab(tabs.Tab): class OverviewTab(project_tabs.OverviewTab):
name = _("Overview") template_name = "admin/networks/ports/_detail_overview.html"
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 PortDetailTabs(tabs.TabGroup): class PortDetailTabs(project_tabs.PortDetailTabs):
slug = "port_details"
tabs = (OverviewTab,) tabs = (OverviewTab,)

View File

@ -15,7 +15,7 @@
from django.conf.urls import patterns from django.conf.urls import patterns
from django.conf.urls import url 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$' PORTS = r'^(?P<port_id>[^/]+)/%s$'
VIEW_MOD = 'openstack_dashboard.dashboards.admin.networks.ports.views' 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 horizon.utils import memoized
from openstack_dashboard import api 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 \ from openstack_dashboard.dashboards.project.networks.ports \
import views as project_views import views as project_views
from openstack_dashboard.dashboards.admin.networks.ports \
import forms as project_forms
class CreateView(forms.ModalFormView): class CreateView(forms.ModalFormView):
form_class = project_forms.CreatePort form_class = ports_forms.CreatePort
form_id = "create_port_form" form_id = "create_port_form"
modal_header = _("Create Port") modal_header = _("Create Port")
template_name = 'admin/networks/ports/create.html'
submit_label = _("Create Port") submit_label = _("Create Port")
submit_url = "horizon:admin:networks:addport" submit_url = "horizon:admin:networks:addport"
success_url = 'horizon:admin:networks:detail'
failure_url = 'horizon:admin:networks:detail'
page_title = _("Create Port") page_title = _("Create Port")
template_name = 'admin/networks/ports/create.html'
url = 'horizon:admin:networks:detail'
def get_success_url(self): def get_success_url(self):
return reverse(self.success_url, return reverse(self.url,
args=(self.kwargs['network_id'],)) args=(self.kwargs['network_id'],))
@memoized.memoized_method @memoized.memoized_method
@ -48,7 +51,7 @@ class CreateView(forms.ModalFormView):
network_id = self.kwargs["network_id"] network_id = self.kwargs["network_id"]
return api.neutron.network_get(self.request, network_id) return api.neutron.network_get(self.request, network_id)
except Exception: except Exception:
redirect = reverse(self.failure_url, redirect = reverse(self.url,
args=(self.kwargs['network_id'],)) args=(self.kwargs['network_id'],))
msg = _("Unable to retrieve network.") msg = _("Unable to retrieve network.")
exceptions.handle(self.request, msg, redirect=redirect) exceptions.handle(self.request, msg, redirect=redirect)
@ -66,9 +69,32 @@ class CreateView(forms.ModalFormView):
"network_name": network.name} "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): class UpdateView(project_views.UpdateView):
form_class = project_forms.UpdatePort form_class = ports_forms.UpdatePort
template_name = 'admin/networks/ports/update.html' template_name = 'admin/networks/ports/update.html'
context_object_name = 'port' context_object_name = 'port'
submit_url = "horizon:admin:networks:editport" submit_url = "horizon:admin:networks:editport"
success_url = 'horizon:admin:networks:detail' 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), api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\ 'mac-learning')\
.AndReturn(mac_learning) .AndReturn(mac_learning)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\
.AndReturn(mac_learning)
self.mox.ReplayAll() self.mox.ReplayAll()
res = self.client.get(reverse('horizon:admin:networks:ports:detail', 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 # admin DetailView is shared with userpanel one, so
# redirection URL on error is userpanel index. # 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) self.assertRedirectsNoFollow(res, redir_url)
@test.create_stubs({api.neutron: ('network_get', @test.create_stubs({api.neutron: ('network_get',
@ -1010,11 +1012,14 @@ class NetworkPortTests(test.BaseAdminViewTests):
def test_port_create_get_with_mac_learning(self): def test_port_create_get_with_mac_learning(self):
self._test_port_create_get(mac_learning=True) 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() network = self.networks.first()
api.neutron.network_get(IsA(http.HttpRequest), api.neutron.network_get(IsA(http.HttpRequest),
network.id)\ network.id)\
.AndReturn(self.networks.first()) .AndReturn(self.networks.first())
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'binding')\
.AndReturn(binding)
api.neutron.is_extension_supported(IsA(http.HttpRequest), api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\ 'mac-learning')\
.AndReturn(mac_learning) .AndReturn(mac_learning)
@ -1036,9 +1041,9 @@ class NetworkPortTests(test.BaseAdminViewTests):
'is_extension_supported', 'is_extension_supported',
'port_create',)}) 'port_create',)})
def test_port_create_post_with_mac_learning(self): 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() network = self.networks.first()
port = self.ports.first() port = self.ports.first()
api.neutron.network_get(IsA(http.HttpRequest), api.neutron.network_get(IsA(http.HttpRequest),
@ -1047,10 +1052,16 @@ class NetworkPortTests(test.BaseAdminViewTests):
api.neutron.network_get(IsA(http.HttpRequest), api.neutron.network_get(IsA(http.HttpRequest),
network.id)\ network.id)\
.AndReturn(self.networks.first()) .AndReturn(self.networks.first())
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'binding')\
.AndReturn(binding)
api.neutron.is_extension_supported(IsA(http.HttpRequest), api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\ 'mac-learning')\
.AndReturn(mac_learning) .AndReturn(mac_learning)
extension_kwargs = {} extension_kwargs = {}
if binding:
extension_kwargs['binding__vnic_type'] = \
port.binding__vnic_type
if mac_learning: if mac_learning:
extension_kwargs['mac_learning_enabled'] = True extension_kwargs['mac_learning_enabled'] = True
api.neutron.port_create(IsA(http.HttpRequest), api.neutron.port_create(IsA(http.HttpRequest),
@ -1060,6 +1071,7 @@ class NetworkPortTests(test.BaseAdminViewTests):
admin_state_up=port.admin_state_up, admin_state_up=port.admin_state_up,
device_id=port.device_id, device_id=port.device_id,
device_owner=port.device_owner, device_owner=port.device_owner,
binding__host_id=port.binding__host_id,
**extension_kwargs)\ **extension_kwargs)\
.AndReturn(port) .AndReturn(port)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -1069,7 +1081,10 @@ class NetworkPortTests(test.BaseAdminViewTests):
'name': port.name, 'name': port.name,
'admin_state': port.admin_state_up, 'admin_state': port.admin_state_up,
'device_id': port.device_id, '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: if mac_learning:
form_data['mac_state'] = True form_data['mac_state'] = True
url = reverse('horizon:admin:networks:addport', url = reverse('horizon:admin:networks:addport',
@ -1093,7 +1108,8 @@ class NetworkPortTests(test.BaseAdminViewTests):
def test_port_create_post_exception_with_mac_learning(self): def test_port_create_post_exception_with_mac_learning(self):
self._test_port_create_post_exception(mac_learning=True) 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() network = self.networks.first()
port = self.ports.first() port = self.ports.first()
api.neutron.network_get(IsA(http.HttpRequest), api.neutron.network_get(IsA(http.HttpRequest),
@ -1102,10 +1118,15 @@ class NetworkPortTests(test.BaseAdminViewTests):
api.neutron.network_get(IsA(http.HttpRequest), api.neutron.network_get(IsA(http.HttpRequest),
network.id)\ network.id)\
.AndReturn(self.networks.first()) .AndReturn(self.networks.first())
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'binding')\
.AndReturn(binding)
api.neutron.is_extension_supported(IsA(http.HttpRequest), api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\ 'mac-learning')\
.AndReturn(mac_learning) .AndReturn(mac_learning)
extension_kwargs = {} extension_kwargs = {}
if binding:
extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
if mac_learning: if mac_learning:
extension_kwargs['mac_learning_enabled'] = True extension_kwargs['mac_learning_enabled'] = True
api.neutron.port_create(IsA(http.HttpRequest), api.neutron.port_create(IsA(http.HttpRequest),
@ -1115,6 +1136,7 @@ class NetworkPortTests(test.BaseAdminViewTests):
admin_state_up=port.admin_state_up, admin_state_up=port.admin_state_up,
device_id=port.device_id, device_id=port.device_id,
device_owner=port.device_owner, device_owner=port.device_owner,
binding__host_id=port.binding__host_id,
**extension_kwargs)\ **extension_kwargs)\
.AndRaise(self.exceptions.neutron) .AndRaise(self.exceptions.neutron)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -1125,7 +1147,10 @@ class NetworkPortTests(test.BaseAdminViewTests):
'admin_state': port.admin_state_up, 'admin_state': port.admin_state_up,
'mac_state': True, 'mac_state': True,
'device_id': port.device_id, '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: if mac_learning:
form_data['mac_learning_enabled'] = True form_data['mac_learning_enabled'] = True
url = reverse('horizon:admin:networks:addport', url = reverse('horizon:admin:networks:addport',
@ -1147,11 +1172,14 @@ class NetworkPortTests(test.BaseAdminViewTests):
def test_port_update_get_with_mac_learning(self): def test_port_update_get_with_mac_learning(self):
self._test_port_update_get(mac_learning=True) 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() port = self.ports.first()
api.neutron.port_get(IsA(http.HttpRequest), api.neutron.port_get(IsA(http.HttpRequest),
port.id)\ port.id)\
.AndReturn(port) .AndReturn(port)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'binding')\
.AndReturn(binding)
api.neutron.is_extension_supported(IsA(http.HttpRequest), api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\ 'mac-learning')\
.AndReturn(mac_learning) .AndReturn(mac_learning)
@ -1175,14 +1203,19 @@ class NetworkPortTests(test.BaseAdminViewTests):
def test_port_update_post_with_mac_learning(self): def test_port_update_post_with_mac_learning(self):
self._test_port_update_post(mac_learning=True) 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() port = self.ports.first()
api.neutron.port_get(IsA(http.HttpRequest), port.id)\ api.neutron.port_get(IsA(http.HttpRequest), port.id)\
.AndReturn(port) .AndReturn(port)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'binding')\
.AndReturn(binding)
api.neutron.is_extension_supported(IsA(http.HttpRequest), api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\ 'mac-learning')\
.AndReturn(mac_learning) .AndReturn(mac_learning)
extension_kwargs = {} extension_kwargs = {}
if binding:
extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
if mac_learning: if mac_learning:
extension_kwargs['mac_learning_enabled'] = True extension_kwargs['mac_learning_enabled'] = True
api.neutron.port_update(IsA(http.HttpRequest), port.id, api.neutron.port_update(IsA(http.HttpRequest), port.id,
@ -1190,6 +1223,7 @@ class NetworkPortTests(test.BaseAdminViewTests):
admin_state_up=port.admin_state_up, admin_state_up=port.admin_state_up,
device_id=port.device_id, device_id=port.device_id,
device_owner=port.device_owner, device_owner=port.device_owner,
binding__host_id=port.binding__host_id,
**extension_kwargs)\ **extension_kwargs)\
.AndReturn(port) .AndReturn(port)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -1199,7 +1233,10 @@ class NetworkPortTests(test.BaseAdminViewTests):
'name': port.name, 'name': port.name,
'admin_state': port.admin_state_up, 'admin_state': port.admin_state_up,
'device_id': port.device_id, '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: if mac_learning:
form_data['mac_state'] = True form_data['mac_state'] = True
url = reverse('horizon:admin:networks:editport', url = reverse('horizon:admin:networks:editport',
@ -1220,16 +1257,22 @@ class NetworkPortTests(test.BaseAdminViewTests):
'is_extension_supported', 'is_extension_supported',
'port_update')}) 'port_update')})
def test_port_update_post_exception_with_mac_learning(self): 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() port = self.ports.first()
api.neutron.port_get(IsA(http.HttpRequest), port.id)\ api.neutron.port_get(IsA(http.HttpRequest), port.id)\
.AndReturn(port) .AndReturn(port)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'binding')\
.AndReturn(binding)
api.neutron.is_extension_supported(IsA(http.HttpRequest), api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\ 'mac-learning')\
.AndReturn(mac_learning) .AndReturn(mac_learning)
extension_kwargs = {} extension_kwargs = {}
if binding:
extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
if mac_learning: if mac_learning:
extension_kwargs['mac_learning_enabled'] = True extension_kwargs['mac_learning_enabled'] = True
api.neutron.port_update(IsA(http.HttpRequest), port.id, api.neutron.port_update(IsA(http.HttpRequest), port.id,
@ -1237,6 +1280,7 @@ class NetworkPortTests(test.BaseAdminViewTests):
admin_state_up=port.admin_state_up, admin_state_up=port.admin_state_up,
device_id=port.device_id, device_id=port.device_id,
device_owner=port.device_owner, device_owner=port.device_owner,
binding__host_id=port.binding__host_id,
**extension_kwargs)\ **extension_kwargs)\
.AndRaise(self.exceptions.neutron) .AndRaise(self.exceptions.neutron)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -1246,7 +1290,10 @@ class NetworkPortTests(test.BaseAdminViewTests):
'name': port.name, 'name': port.name,
'admin_state': port.admin_state_up, 'admin_state': port.admin_state_up,
'device_id': port.device_id, '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: if mac_learning:
form_data['mac_state'] = True form_data['mac_state'] = True
url = reverse('horizon:admin:networks:editport', url = reverse('horizon:admin:networks:editport',

View File

@ -14,6 +14,7 @@
import logging import logging
from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -25,6 +26,8 @@ from openstack_dashboard import api
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
VNIC_TYPES = [('normal', _('Normal')), ('direct', _('Direct')),
('macvtap', _('MacVTap'))]
class UpdatePort(forms.SelfHandlingForm): class UpdatePort(forms.SelfHandlingForm):
@ -42,18 +45,42 @@ class UpdatePort(forms.SelfHandlingForm):
def __init__(self, request, *args, **kwargs): def __init__(self, request, *args, **kwargs):
super(UpdatePort, self).__init__(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'): if api.neutron.is_extension_supported(request, 'mac-learning'):
self.fields['mac_state'] = forms.BooleanField( 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): def handle(self, request, data):
data['admin_state'] = (data['admin_state'] == 'True') data['admin_state'] = (data['admin_state'] == 'True')
try: try:
LOG.debug('params = %s' % data) LOG.debug('params = %s' % data)
extension_kwargs = {} extension_kwargs = {}
if 'binding__vnic_type' in data:
extension_kwargs['binding__vnic_type'] = \
data['binding__vnic_type']
if 'mac_state' in data: if 'mac_state' in data:
extension_kwargs['mac_learning_enabled'] = data['mac_state'] 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'], name=data['name'],
admin_state_up=data['admin_state'], admin_state_up=data['admin_state'],
**extension_kwargs) **extension_kwargs)

View File

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

View File

@ -1,11 +1,7 @@
{% load i18n sizeformat %} {% load i18n sizeformat %}
{% load url from future %} {% load url from future %}
<h3>{% trans "Port Overview" %}</h3> <div class="detail">
<div class="info row detail">
<h4>{% trans "Port" %}</h4>
<hr class="header_rule">
<dl class="dl-horizontal"> <dl class="dl-horizontal">
<dt>{% trans "Name" %}</dt> <dt>{% trans "Name" %}</dt>
<dd>{{ port.name|default:_("None") }}</dd> <dd>{{ port.name|default:_("None") }}</dd>
@ -16,18 +12,7 @@
<dd><a href="{{ network_url }}">{{ port.network_id|default:_("None") }}</a></dd> <dd><a href="{{ network_url }}">{{ port.network_id|default:_("None") }}</a></dd>
<dt>{% trans "Project ID" %}</dt> <dt>{% trans "Project ID" %}</dt>
<dd>{{ port.tenant_id|default:_("-") }}</dd> <dd>{{ port.tenant_id|default:_("-") }}</dd>
<dt>{% trans "Fixed IP" %}</dt> <dt>{% trans "MAC Address" %}</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>
<dd>{{ port.mac_address|default:_("None") }}</dd> <dd>{{ port.mac_address|default:_("None") }}</dd>
<dt>{% trans "Status" %}</dt> <dt>{% trans "Status" %}</dt>
<dd>{{ port.status_label|default:_("None") }}</dd> <dd>{{ port.status_label|default:_("None") }}</dd>
@ -37,12 +22,34 @@
<dt>{% trans "MAC Learning State" %}</dt> <dt>{% trans "MAC Learning State" %}</dt>
<dd>{{ port.mac_state }}</dd> <dd>{{ port.mac_state }}</dd>
{% endif %} {% 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 %} {% if port.device_id|length > 1 or port.device_owner %}
<dd><b>{% trans "Device Owner" %}</b>: {{ port.device_owner|default:_("None") }}</dd> <dt>{% trans "Device Owner" %}</dt>
<dd><b>{% trans "Device ID" %}</b>: {{ port.device_id|default:_("-") }}</dd> <dd>{{ port.device_owner|default:_("None") }}</dd>
<dt>{% trans "Device ID" %}</dt>
<dd>{{ port.device_id|default:_("None") }}</dd>
{% else %} {% else %}
<dd>{% trans "No attached device" %}</dd> <dd>{% trans "No attached device" %}</dd>
{% endif %} {% 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> </dl>
</div> </div>

View File

@ -1691,11 +1691,14 @@ class NetworkPortTests(test.TestCase):
def test_port_update_get_with_mac_learning(self): def test_port_update_get_with_mac_learning(self):
self._test_port_update_get(mac_learning=True) 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() port = self.ports.first()
api.neutron.port_get(IsA(http.HttpRequest), api.neutron.port_get(IsA(http.HttpRequest),
port.id)\ port.id)\
.AndReturn(port) .AndReturn(port)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'binding')\
.AndReturn(binding)
api.neutron.is_extension_supported(IsA(http.HttpRequest), api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\ 'mac-learning')\
.AndReturn(mac_learning) .AndReturn(mac_learning)
@ -1719,14 +1722,19 @@ class NetworkPortTests(test.TestCase):
def test_port_update_post_with_mac_learning(self): def test_port_update_post_with_mac_learning(self):
self._test_port_update_post(mac_learning=True) 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() port = self.ports.first()
api.neutron.port_get(IsA(http.HttpRequest), port.id)\ api.neutron.port_get(IsA(http.HttpRequest), port.id)\
.AndReturn(port) .AndReturn(port)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'binding')\
.AndReturn(binding)
api.neutron.is_extension_supported(IsA(http.HttpRequest), api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\ 'mac-learning')\
.AndReturn(mac_learning) .AndReturn(mac_learning)
extension_kwargs = {} extension_kwargs = {}
if binding:
extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
if mac_learning: if mac_learning:
extension_kwargs['mac_learning_enabled'] = True extension_kwargs['mac_learning_enabled'] = True
api.neutron.port_update(IsA(http.HttpRequest), port.id, api.neutron.port_update(IsA(http.HttpRequest), port.id,
@ -1740,6 +1748,8 @@ class NetworkPortTests(test.TestCase):
'port_id': port.id, 'port_id': port.id,
'name': port.name, 'name': port.name,
'admin_state': port.admin_state_up} 'admin_state': port.admin_state_up}
if binding:
form_data['binding__vnic_type'] = port.binding__vnic_type
if mac_learning: if mac_learning:
form_data['mac_state'] = True form_data['mac_state'] = True
url = reverse('horizon:project:networks:editport', url = reverse('horizon:project:networks:editport',
@ -1762,14 +1772,21 @@ class NetworkPortTests(test.TestCase):
def test_port_update_post_exception_with_mac_learning(self): 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)
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() port = self.ports.first()
api.neutron.port_get(IsA(http.HttpRequest), port.id)\ api.neutron.port_get(IsA(http.HttpRequest), port.id)\
.AndReturn(port) .AndReturn(port)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'binding')\
.AndReturn(binding)
api.neutron.is_extension_supported(IsA(http.HttpRequest), api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\ 'mac-learning')\
.AndReturn(mac_learning) .AndReturn(mac_learning)
extension_kwargs = {} extension_kwargs = {}
if binding:
extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
if mac_learning: if mac_learning:
extension_kwargs['mac_learning_enabled'] = True extension_kwargs['mac_learning_enabled'] = True
api.neutron.port_update(IsA(http.HttpRequest), port.id, api.neutron.port_update(IsA(http.HttpRequest), port.id,
@ -1783,6 +1800,8 @@ class NetworkPortTests(test.TestCase):
'port_id': port.id, 'port_id': port.id,
'name': port.name, 'name': port.name,
'admin_state': port.admin_state_up} 'admin_state': port.admin_state_up}
if binding:
form_data['binding__vnic_type'] = port.binding__vnic_type
if mac_learning: if mac_learning:
form_data['mac_state'] = True form_data['mac_state'] = True
url = reverse('horizon:project:networks:editport', 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. # in this list will be available to choose from when creating a network.
# Network types include local, flat, vlan, gre, and vxlan. # Network types include local, flat, vlan, gre, and vxlan.
'supported_provider_types': ['*'], '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 # The OPENSTACK_IMAGE_BACKEND settings can be used to customize features

View File

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