Browse Source

Merge "Adds initial SRIOV creation/config support"

Jenkins 4 years ago
parent
commit
a174b4294d

+ 14
- 0
doc/source/topics/settings.rst View File

@@ -678,6 +678,7 @@ Default::
678 678
             'enable_vpn': True,
679 679
             'profile_support': None,
680 680
             'supported_provider_types': ["*"],
681
+            'supported_vnic_types': ["*"],
681 682
             'segmentation_id_range': {}
682 683
         }
683 684
 
@@ -803,6 +804,19 @@ be available to choose from.
803 804
 
804 805
 Example: ``['local', 'flat', 'gre']``
805 806
 
807
+``supported_vnic_types``:
808
+
809
+.. versionadded:: 2015.1(Kilo)
810
+
811
+Default ``['*']``
812
+
813
+For use with the port binding extension. Use this to explicitly set which VNIC
814
+types are supported; only those listed will be shown when creating or editing
815
+a port. VNIC types include normal, direct and macvtap. By default all VNIC
816
+types will be available to choose from.
817
+
818
+Example ``['normal', 'direct']``
819
+
806 820
 ``segmentation_id_range``:
807 821
 
808 822
 .. versionadded:: 2014.2(Juno)

+ 15
- 2
openstack_dashboard/api/neutron.py View File

@@ -88,9 +88,9 @@ class Network(NeutronAPIDictWrapper):
88 88
     def __init__(self, apiresource):
89 89
         apiresource['admin_state'] = \
90 90
             'UP' if apiresource['admin_state_up'] else 'DOWN'
91
-        # Django cannot handle a key name with a colon, so remap another key
91
+        # Django cannot handle a key name with ':', so use '__'
92 92
         for key in apiresource.keys():
93
-            if key.find(':'):
93
+            if ':' in key:
94 94
                 apiresource['__'.join(key.split(':'))] = apiresource[key]
95 95
         super(Network, self).__init__(apiresource)
96 96
 
@@ -112,6 +112,10 @@ class Port(NeutronAPIDictWrapper):
112 112
     """Wrapper for neutron ports."""
113 113
 
114 114
     def __init__(self, apiresource):
115
+        # Django cannot handle a key name with ':', so use '__'
116
+        for key in apiresource.keys():
117
+            if ':' in key:
118
+                apiresource['__'.join(key.split(':'))] = apiresource[key]
115 119
         apiresource['admin_state'] = \
116 120
             'UP' if apiresource['admin_state_up'] else 'DOWN'
117 121
         if 'mac_learning_enabled' in apiresource:
@@ -729,6 +733,13 @@ def port_get(request, port_id, **params):
729 733
     return Port(port)
730 734
 
731 735
 
736
+def unescape_port_kwargs(**kwargs):
737
+    for key in kwargs:
738
+        if '__' in key:
739
+            kwargs[':'.join(key.split('__'))] = kwargs.pop(key)
740
+    return kwargs
741
+
742
+
732 743
 def port_create(request, network_id, **kwargs):
733 744
     """Create a port on a specified network.
734 745
 
@@ -743,6 +754,7 @@ def port_create(request, network_id, **kwargs):
743 754
     # In the case policy profiles are being used, profile id is needed.
744 755
     if 'policy_profile_id' in kwargs:
745 756
         kwargs['n1kv:profile_id'] = kwargs.pop('policy_profile_id')
757
+    kwargs = unescape_port_kwargs(**kwargs)
746 758
     body = {'port': {'network_id': network_id}}
747 759
     if 'tenant_id' not in kwargs:
748 760
         kwargs['tenant_id'] = request.user.project_id
@@ -758,6 +770,7 @@ def port_delete(request, port_id):
758 770
 
759 771
 def port_update(request, port_id, **kwargs):
760 772
     LOG.debug("port_update(): portid=%s, kwargs=%s" % (port_id, kwargs))
773
+    kwargs = unescape_port_kwargs(**kwargs)
761 774
     body = {'port': kwargs}
762 775
     port = neutronclient(request).update_port(port_id, body=body).get('port')
763 776
     return Port(port)

+ 47
- 2
openstack_dashboard/dashboards/admin/networks/ports/forms.py View File

@@ -14,6 +14,7 @@
14 14
 
15 15
 import logging
16 16
 
17
+from django.conf import settings
17 18
 from django.core.urlresolvers import reverse
18 19
 from django.utils.translation import ugettext_lazy as _
19 20
 
@@ -27,6 +28,8 @@ from openstack_dashboard.dashboards.project.networks.ports \
27 28
 
28 29
 
29 30
 LOG = logging.getLogger(__name__)
31
+VNIC_TYPES = [('normal', _('Normal')), ('direct', _('Direct')),
32
+              ('macvtap', _('MacVTap'))]
30 33
 
31 34
 
32 35
 class CreatePort(forms.SelfHandlingForm):
@@ -49,9 +52,36 @@ class CreatePort(forms.SelfHandlingForm):
49 52
                                    help_text=_("Device owner attached to the "
50 53
                                                "port"),
51 54
                                    required=False)
55
+    binding__host_id = forms.CharField(
56
+        label=_("Binding: Host"),
57
+        help_text=_("The ID of the host where the port is allocated. In some "
58
+                    "cases, different implementations can run on different "
59
+                    "hosts."),
60
+        required=False)
61
+
62
+    failure_url = 'horizon:admin:networks:detail'
52 63
 
53 64
     def __init__(self, request, *args, **kwargs):
54 65
         super(CreatePort, self).__init__(request, *args, **kwargs)
66
+        if api.neutron.is_extension_supported(request, 'binding'):
67
+            neutron_settings = getattr(settings,
68
+                                       'OPENSTACK_NEUTRON_NETWORK', {})
69
+            supported_vnic_types = neutron_settings.get(
70
+                'supported_vnic_types', ['*'])
71
+            if supported_vnic_types == ['*']:
72
+                vnic_type_choices = VNIC_TYPES
73
+            else:
74
+                vnic_type_choices = [
75
+                    vnic_type for vnic_type in VNIC_TYPES
76
+                    if vnic_type[0] in supported_vnic_types
77
+                ]
78
+
79
+            self.fields['binding__vnic_type'] = forms.ChoiceField(
80
+                choices=vnic_type_choices,
81
+                label=_("Binding: VNIC Type"),
82
+                help_text=_("The VNIC type that is bound to the neutron port"),
83
+                required=False)
84
+
55 85
         if api.neutron.is_extension_supported(request, 'mac-learning'):
56 86
             self.fields['mac_state'] = forms.BooleanField(
57 87
                 label=_("MAC Learning State"), initial=False, required=False)
@@ -78,7 +108,7 @@ class CreatePort(forms.SelfHandlingForm):
78 108
             msg = _('Failed to create a port for network %s') \
79 109
                 % data['network_id']
80 110
             LOG.info(msg)
81
-            redirect = reverse('horizon:admin:networks:detail',
111
+            redirect = reverse(self.failure_url,
82 112
                                args=(data['network_id'],))
83 113
             exceptions.handle(request, msg, redirect=redirect)
84 114
 
@@ -92,6 +122,13 @@ class UpdatePort(project_forms.UpdatePort):
92 122
                                    help_text=_("Device owner attached to the "
93 123
                                                "port"),
94 124
                                    required=False)
125
+    binding__host_id = forms.CharField(
126
+        label=_("Binding: Host"),
127
+        help_text=_("The ID of the host where the port is allocated. In some "
128
+                    "cases, different implementations can run on different "
129
+                    "hosts."),
130
+        required=False)
131
+
95 132
     failure_url = 'horizon:admin:networks:detail'
96 133
 
97 134
     def handle(self, request, data):
@@ -99,13 +136,21 @@ class UpdatePort(project_forms.UpdatePort):
99 136
             LOG.debug('params = %s' % data)
100 137
             extension_kwargs = {}
101 138
             data['admin_state'] = (data['admin_state'] == 'True')
139
+            if 'binding__vnic_type' in data:
140
+                extension_kwargs['binding__vnic_type'] = \
141
+                    data['binding__vnic_type']
142
+
102 143
             if 'mac_state' in data:
103 144
                 extension_kwargs['mac_learning_enabled'] = data['mac_state']
104
-            port = api.neutron.port_update(request, data['port_id'],
145
+
146
+            port = api.neutron.port_update(request,
147
+                                           data['port_id'],
105 148
                                            name=data['name'],
106 149
                                            admin_state_up=data['admin_state'],
107 150
                                            device_id=data['device_id'],
108 151
                                            device_owner=data['device_owner'],
152
+                                           binding__host_id=data
153
+                                           ['binding__host_id'],
109 154
                                            **extension_kwargs)
110 155
             msg = _('Port %s was successfully updated.') % data['port_id']
111 156
             LOG.debug(msg)

+ 2
- 31
openstack_dashboard/dashboards/admin/networks/ports/tables.py View File

@@ -73,36 +73,14 @@ class CreatePort(tables.LinkAction):
73 73
         return reverse(self.url, args=(network_id,))
74 74
 
75 75
 
76
-class UpdatePort(policy.PolicyTargetMixin, tables.LinkAction):
77
-    name = "update"
78
-    verbose_name = _("Edit Port")
76
+class UpdatePort(project_tables.UpdatePort):
79 77
     url = "horizon:admin:networks:editport"
80
-    classes = ("ajax-modal",)
81
-    icon = "pencil"
82
-    policy_rules = (("network", "update_port"),)
83
-
84
-    def get_link_url(self, port):
85
-        network_id = self.table.kwargs['network_id']
86
-        return reverse(self.url, args=(network_id, port.id))
87 78
 
88 79
 
89
-class PortsTable(tables.DataTable):
80
+class PortsTable(project_tables.PortsTable):
90 81
     name = tables.Column("name_or_id",
91 82
                          verbose_name=_("Name"),
92 83
                          link="horizon:admin:networks:ports:detail")
93
-    fixed_ips = tables.Column(
94
-        project_tables.get_fixed_ips, verbose_name=_("Fixed IPs"))
95
-    device_id = tables.Column(
96
-        project_tables.get_attached, verbose_name=_("Device Attached"))
97
-    status = tables.Column(
98
-        "status",
99
-        verbose_name=_("Status"),
100
-        display_choices=project_tables.STATUS_DISPLAY_CHOICES)
101
-    admin_state = tables.Column("admin_state",
102
-                                verbose_name=_("Admin State"),
103
-                                display_choices=project_tables.DISPLAY_CHOICES)
104
-    mac_state = tables.Column("mac_state", empty_value=api.neutron.OFF_STATE,
105
-                              verbose_name=_("Mac Learning State"))
106 84
 
107 85
     class Meta(object):
108 86
         name = "ports"
@@ -110,10 +88,3 @@ class PortsTable(tables.DataTable):
110 88
         table_actions = (CreatePort, DeletePort)
111 89
         row_actions = (UpdatePort, DeletePort,)
112 90
         hidden_title = False
113
-
114
-    def __init__(self, request, data=None, needs_form_wrapper=None, **kwargs):
115
-        super(PortsTable, self).__init__(request, data=data,
116
-                                         needs_form_wrapper=needs_form_wrapper,
117
-                                         **kwargs)
118
-        if not api.neutron.is_extension_supported(request, 'mac-learning'):
119
-            del self.columns['mac_state']

+ 5
- 23
openstack_dashboard/dashboards/admin/networks/ports/tabs.py View File

@@ -12,31 +12,13 @@
12 12
 #    License for the specific language governing permissions and limitations
13 13
 #    under the License.
14 14
 
15
-from django.core.urlresolvers import reverse
16
-from django.utils.translation import ugettext_lazy as _
15
+from openstack_dashboard.dashboards.project.networks.ports \
16
+    import tabs as project_tabs
17 17
 
18
-from horizon import exceptions
19
-from horizon import tabs
20 18
 
21
-from openstack_dashboard import api
19
+class OverviewTab(project_tabs.OverviewTab):
20
+    template_name = "admin/networks/ports/_detail_overview.html"
22 21
 
23 22
 
24
-class OverviewTab(tabs.Tab):
25
-    name = _("Overview")
26
-    slug = "overview"
27
-    template_name = "project/networks/ports/_detail_overview.html"
28
-
29
-    def get_context_data(self, request):
30
-        port_id = self.tab_group.kwargs['port_id']
31
-        try:
32
-            port = api.neutron.port_get(self.request, port_id)
33
-        except Exception:
34
-            redirect = reverse('horizon:admin:networks:index')
35
-            msg = _('Unable to retrieve port details.')
36
-            exceptions.handle(request, msg, redirect=redirect)
37
-        return {'port': port}
38
-
39
-
40
-class PortDetailTabs(tabs.TabGroup):
41
-    slug = "port_details"
23
+class PortDetailTabs(project_tabs.PortDetailTabs):
42 24
     tabs = (OverviewTab,)

+ 1
- 1
openstack_dashboard/dashboards/admin/networks/ports/urls.py View File

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

+ 36
- 10
openstack_dashboard/dashboards/admin/networks/ports/views.py View File

@@ -20,26 +20,29 @@ from horizon import forms
20 20
 from horizon.utils import memoized
21 21
 
22 22
 from openstack_dashboard import api
23
-from openstack_dashboard.dashboards.project.networks.ports \
24
-    import views as project_views
25 23
 
26 24
 from openstack_dashboard.dashboards.admin.networks.ports \
27
-    import forms as project_forms
25
+    import forms as ports_forms
26
+from openstack_dashboard.dashboards.admin.networks.ports \
27
+    import tables as ports_tables
28
+from openstack_dashboard.dashboards.admin.networks.ports \
29
+    import tabs as ports_tabs
30
+from openstack_dashboard.dashboards.project.networks.ports \
31
+    import views as project_views
28 32
 
29 33
 
30 34
 class CreateView(forms.ModalFormView):
31
-    form_class = project_forms.CreatePort
35
+    form_class = ports_forms.CreatePort
32 36
     form_id = "create_port_form"
33 37
     modal_header = _("Create Port")
34
-    template_name = 'admin/networks/ports/create.html'
35 38
     submit_label = _("Create Port")
36 39
     submit_url = "horizon:admin:networks:addport"
37
-    success_url = 'horizon:admin:networks:detail'
38
-    failure_url = 'horizon:admin:networks:detail'
39 40
     page_title = _("Create Port")
41
+    template_name = 'admin/networks/ports/create.html'
42
+    url = 'horizon:admin:networks:detail'
40 43
 
41 44
     def get_success_url(self):
42
-        return reverse(self.success_url,
45
+        return reverse(self.url,
43 46
                        args=(self.kwargs['network_id'],))
44 47
 
45 48
     @memoized.memoized_method
@@ -48,7 +51,7 @@ class CreateView(forms.ModalFormView):
48 51
             network_id = self.kwargs["network_id"]
49 52
             return api.neutron.network_get(self.request, network_id)
50 53
         except Exception:
51
-            redirect = reverse(self.failure_url,
54
+            redirect = reverse(self.url,
52 55
                                args=(self.kwargs['network_id'],))
53 56
             msg = _("Unable to retrieve network.")
54 57
             exceptions.handle(self.request, msg, redirect=redirect)
@@ -66,9 +69,32 @@ class CreateView(forms.ModalFormView):
66 69
                 "network_name": network.name}
67 70
 
68 71
 
72
+class DetailView(project_views.DetailView):
73
+    tab_group_class = ports_tabs.PortDetailTabs
74
+
75
+    def get_context_data(self, **kwargs):
76
+        context = super(DetailView, self).get_context_data(**kwargs)
77
+        port = context["port"]
78
+        table = ports_tables.PortsTable(self.request,
79
+                                        network_id=port.network_id)
80
+        context["url"] = reverse('horizon:admin:networks:index')
81
+        context["actions"] = table.render_row_actions(port)
82
+        return context
83
+
84
+    @staticmethod
85
+    def get_redirect_url():
86
+        return reverse('horizon:admin:networks:index')
87
+
88
+
69 89
 class UpdateView(project_views.UpdateView):
70
-    form_class = project_forms.UpdatePort
90
+    form_class = ports_forms.UpdatePort
71 91
     template_name = 'admin/networks/ports/update.html'
72 92
     context_object_name = 'port'
73 93
     submit_url = "horizon:admin:networks:editport"
74 94
     success_url = 'horizon:admin:networks:detail'
95
+
96
+    def get_initial(self):
97
+        initial = super(UpdateView, self).get_initial()
98
+        port = self._get_object()
99
+        initial['binding__host_id'] = port['binding__host_id']
100
+        return initial

+ 73
- 0
openstack_dashboard/dashboards/admin/networks/templates/networks/ports/_detail_overview.html View File

@@ -0,0 +1,73 @@
1
+{% load i18n sizeformat %}
2
+{% load url from future %}
3
+
4
+<div class="detail">
5
+  <dl class="dl-horizontal">
6
+    <dt>{% trans "Name" %}</dt>
7
+    <dd>{{ port.name|default:_("None") }}</dd>
8
+    <dt>{% trans "ID" %}</dt>
9
+    <dd>{{ port.id|default:_("None") }}</dd>
10
+    {% url 'horizon:project:networks:detail' port.network_id as network_url %}
11
+    <dt>{% trans "Network ID" %}</dt>
12
+    <dd><a href="{{ network_url }}">{{ port.network_id|default:_("None") }}</a></dd>
13
+    <dt>{% trans "Project ID" %}</dt>
14
+    <dd>{{ port.tenant_id|default:_("-") }}</dd>
15
+    <dt>{% trans "MAC Address" %}</dt>
16
+    <dd>{{ port.mac_address|default:_("None") }}</dd>
17
+    <dt>{% trans "Status" %}</dt>
18
+    <dd>{{ port.status_label|default:_("None") }}</dd>
19
+    <dt>{% trans "Admin State" %}</dt>
20
+    <dd>{{ port.admin_state_label|default:_("None") }}</dd>
21
+    {% if port.mac_state %}
22
+    <dt>{% trans "MAC Learning State" %}</dt>
23
+    <dd>{% trans "On" %}</dd>
24
+    {% endif %}
25
+    <h4>{% trans "Fixed IP" %}</h4>
26
+    <hr class="header_rule">
27
+    {% if port.fixed_ips.items|length > 1 %}
28
+      {% for ip in port.fixed_ips %}
29
+      <dt>{% trans "IP Address" %}</dt>
30
+      <dd>{{ ip.ip_address }}</dd>
31
+      {% url 'horizon:project:networks:subnets:detail' ip.subnet_id as subnet_url %}
32
+      <dt>{% trans "Subnet ID" %}</dt>
33
+      <dd><a href="{{ subnet_url }}">{{ ip.subnet_id }}</a></dd>
34
+      {% endfor %}
35
+    {% else %}
36
+      <dd>{% trans "None" %}</dd>
37
+    {% endif %}
38
+    <h4>{% trans "Attached Device" %}</h4>
39
+    <hr class="header_rule">
40
+    {% if port.device_id|length > 1 or port.device_owner %}
41
+      <dt>{% trans "Device Owner" %}</dt>
42
+      <dd>{{ port.device_owner|default:_("None") }}</dd>
43
+      <dt>{% trans "Device ID" %}</dt>
44
+      <dd>{{ port.device_id|default:_("None") }}</dd>
45
+    {% else %}
46
+      <dd>{% trans "No attached device" %}</dd>
47
+    {% endif %}
48
+    <h4>{% trans "Binding" %}</h4>
49
+    <hr class="header_rule">
50
+    <dt>{% trans "Host" %}</dt>
51
+    <dd>{{ port.binding__host_id|default:_("None") }}</dd>
52
+    <dt>{% trans "Profile" %}</dt>
53
+    <dd>{{ port.binding__profile|default:_("None") }}</dd>
54
+    <dt>{% trans "VIF Type" %}</dt>
55
+    <dd>{{ port.binding__vif_type|replace_underscores }}</dd>
56
+    <dt>{% trans "VIF Details" %}</dt>
57
+    {% if port.binding__vif_details.items %}
58
+      <dd>
59
+        <ul>
60
+        {% for key,value in port.binding__vif_details.items %}
61
+        <li><b>{{ key }}</b> {{ value }}</li>
62
+        {% endfor %}
63
+        </ul>
64
+      </dd>
65
+    {% else %}
66
+      <dd>{% trans "None" %}</dd>
67
+    {% endif %}
68
+    {% if port.binding__vnic_type %}
69
+      <dt>{% trans "VNIC Type" %}</dt>
70
+      <dd>{{ port.binding__vnic_type }}</dd>
71
+    {% endif %}
72
+  </dl>
73
+</div>

+ 61
- 14
openstack_dashboard/dashboards/admin/networks/tests.py View File

@@ -975,7 +975,9 @@ class NetworkPortTests(test.BaseAdminViewTests):
975 975
         api.neutron.is_extension_supported(IsA(http.HttpRequest),
976 976
                                            'mac-learning')\
977 977
             .AndReturn(mac_learning)
978
-
978
+        api.neutron.is_extension_supported(IsA(http.HttpRequest),
979
+                                           'mac-learning')\
980
+            .AndReturn(mac_learning)
979 981
         self.mox.ReplayAll()
980 982
 
981 983
         res = self.client.get(reverse('horizon:admin:networks:ports:detail',
@@ -997,7 +999,7 @@ class NetworkPortTests(test.BaseAdminViewTests):
997 999
 
998 1000
         # admin DetailView is shared with userpanel one, so
999 1001
         # redirection URL on error is userpanel index.
1000
-        redir_url = reverse('horizon:project:networks:index')
1002
+        redir_url = reverse('horizon:admin:networks:index')
1001 1003
         self.assertRedirectsNoFollow(res, redir_url)
1002 1004
 
1003 1005
     @test.create_stubs({api.neutron: ('network_get',
@@ -1010,11 +1012,14 @@ class NetworkPortTests(test.BaseAdminViewTests):
1010 1012
     def test_port_create_get_with_mac_learning(self):
1011 1013
         self._test_port_create_get(mac_learning=True)
1012 1014
 
1013
-    def _test_port_create_get(self, mac_learning=False):
1015
+    def _test_port_create_get(self, mac_learning=False, binding=False):
1014 1016
         network = self.networks.first()
1015 1017
         api.neutron.network_get(IsA(http.HttpRequest),
1016 1018
                                 network.id)\
1017 1019
             .AndReturn(self.networks.first())
1020
+        api.neutron.is_extension_supported(IsA(http.HttpRequest),
1021
+                                           'binding')\
1022
+            .AndReturn(binding)
1018 1023
         api.neutron.is_extension_supported(IsA(http.HttpRequest),
1019 1024
                                            'mac-learning')\
1020 1025
             .AndReturn(mac_learning)
@@ -1036,9 +1041,9 @@ class NetworkPortTests(test.BaseAdminViewTests):
1036 1041
                                       'is_extension_supported',
1037 1042
                                       'port_create',)})
1038 1043
     def test_port_create_post_with_mac_learning(self):
1039
-        self._test_port_create_post(mac_learning=True)
1044
+        self._test_port_create_post(mac_learning=True, binding=False)
1040 1045
 
1041
-    def _test_port_create_post(self, mac_learning=False):
1046
+    def _test_port_create_post(self, mac_learning=False, binding=False):
1042 1047
         network = self.networks.first()
1043 1048
         port = self.ports.first()
1044 1049
         api.neutron.network_get(IsA(http.HttpRequest),
@@ -1047,10 +1052,16 @@ class NetworkPortTests(test.BaseAdminViewTests):
1047 1052
         api.neutron.network_get(IsA(http.HttpRequest),
1048 1053
                                 network.id)\
1049 1054
             .AndReturn(self.networks.first())
1055
+        api.neutron.is_extension_supported(IsA(http.HttpRequest),
1056
+                                           'binding')\
1057
+            .AndReturn(binding)
1050 1058
         api.neutron.is_extension_supported(IsA(http.HttpRequest),
1051 1059
                                            'mac-learning')\
1052 1060
             .AndReturn(mac_learning)
1053 1061
         extension_kwargs = {}
1062
+        if binding:
1063
+            extension_kwargs['binding__vnic_type'] = \
1064
+                port.binding__vnic_type
1054 1065
         if mac_learning:
1055 1066
             extension_kwargs['mac_learning_enabled'] = True
1056 1067
         api.neutron.port_create(IsA(http.HttpRequest),
@@ -1060,6 +1071,7 @@ class NetworkPortTests(test.BaseAdminViewTests):
1060 1071
                                 admin_state_up=port.admin_state_up,
1061 1072
                                 device_id=port.device_id,
1062 1073
                                 device_owner=port.device_owner,
1074
+                                binding__host_id=port.binding__host_id,
1063 1075
                                 **extension_kwargs)\
1064 1076
             .AndReturn(port)
1065 1077
         self.mox.ReplayAll()
@@ -1069,7 +1081,10 @@ class NetworkPortTests(test.BaseAdminViewTests):
1069 1081
                      'name': port.name,
1070 1082
                      'admin_state': port.admin_state_up,
1071 1083
                      'device_id': port.device_id,
1072
-                     'device_owner': port.device_owner}
1084
+                     'device_owner': port.device_owner,
1085
+                     'binding__host_id': port.binding__host_id}
1086
+        if binding:
1087
+            form_data['binding__vnic_type'] = port.binding__vnic_type
1073 1088
         if mac_learning:
1074 1089
             form_data['mac_state'] = True
1075 1090
         url = reverse('horizon:admin:networks:addport',
@@ -1093,7 +1108,8 @@ class NetworkPortTests(test.BaseAdminViewTests):
1093 1108
     def test_port_create_post_exception_with_mac_learning(self):
1094 1109
         self._test_port_create_post_exception(mac_learning=True)
1095 1110
 
1096
-    def _test_port_create_post_exception(self, mac_learning=False):
1111
+    def _test_port_create_post_exception(self, mac_learning=False,
1112
+                                         binding=False):
1097 1113
         network = self.networks.first()
1098 1114
         port = self.ports.first()
1099 1115
         api.neutron.network_get(IsA(http.HttpRequest),
@@ -1102,10 +1118,15 @@ class NetworkPortTests(test.BaseAdminViewTests):
1102 1118
         api.neutron.network_get(IsA(http.HttpRequest),
1103 1119
                                 network.id)\
1104 1120
             .AndReturn(self.networks.first())
1121
+        api.neutron.is_extension_supported(IsA(http.HttpRequest),
1122
+                                           'binding')\
1123
+            .AndReturn(binding)
1105 1124
         api.neutron.is_extension_supported(IsA(http.HttpRequest),
1106 1125
                                            'mac-learning')\
1107 1126
             .AndReturn(mac_learning)
1108 1127
         extension_kwargs = {}
1128
+        if binding:
1129
+            extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
1109 1130
         if mac_learning:
1110 1131
             extension_kwargs['mac_learning_enabled'] = True
1111 1132
         api.neutron.port_create(IsA(http.HttpRequest),
@@ -1115,6 +1136,7 @@ class NetworkPortTests(test.BaseAdminViewTests):
1115 1136
                                 admin_state_up=port.admin_state_up,
1116 1137
                                 device_id=port.device_id,
1117 1138
                                 device_owner=port.device_owner,
1139
+                                binding__host_id=port.binding__host_id,
1118 1140
                                 **extension_kwargs)\
1119 1141
             .AndRaise(self.exceptions.neutron)
1120 1142
         self.mox.ReplayAll()
@@ -1125,7 +1147,10 @@ class NetworkPortTests(test.BaseAdminViewTests):
1125 1147
                      'admin_state': port.admin_state_up,
1126 1148
                      'mac_state': True,
1127 1149
                      'device_id': port.device_id,
1128
-                     'device_owner': port.device_owner}
1150
+                     'device_owner': port.device_owner,
1151
+                     'binding__host_id': port.binding__host_id}
1152
+        if binding:
1153
+            form_data['binding__vnic_type'] = port.binding__vnic_type
1129 1154
         if mac_learning:
1130 1155
             form_data['mac_learning_enabled'] = True
1131 1156
         url = reverse('horizon:admin:networks:addport',
@@ -1147,11 +1172,14 @@ class NetworkPortTests(test.BaseAdminViewTests):
1147 1172
     def test_port_update_get_with_mac_learning(self):
1148 1173
         self._test_port_update_get(mac_learning=True)
1149 1174
 
1150
-    def _test_port_update_get(self, mac_learning=False):
1175
+    def _test_port_update_get(self, mac_learning=False, binding=False):
1151 1176
         port = self.ports.first()
1152 1177
         api.neutron.port_get(IsA(http.HttpRequest),
1153 1178
                              port.id)\
1154 1179
             .AndReturn(port)
1180
+        api.neutron.is_extension_supported(IsA(http.HttpRequest),
1181
+                                           'binding')\
1182
+            .AndReturn(binding)
1155 1183
         api.neutron.is_extension_supported(IsA(http.HttpRequest),
1156 1184
                                            'mac-learning')\
1157 1185
             .AndReturn(mac_learning)
@@ -1175,14 +1203,19 @@ class NetworkPortTests(test.BaseAdminViewTests):
1175 1203
     def test_port_update_post_with_mac_learning(self):
1176 1204
         self._test_port_update_post(mac_learning=True)
1177 1205
 
1178
-    def _test_port_update_post(self, mac_learning=False):
1206
+    def _test_port_update_post(self, mac_learning=False, binding=False):
1179 1207
         port = self.ports.first()
1180 1208
         api.neutron.port_get(IsA(http.HttpRequest), port.id)\
1181 1209
             .AndReturn(port)
1210
+        api.neutron.is_extension_supported(IsA(http.HttpRequest),
1211
+                                           'binding')\
1212
+            .AndReturn(binding)
1182 1213
         api.neutron.is_extension_supported(IsA(http.HttpRequest),
1183 1214
                                            'mac-learning')\
1184 1215
             .AndReturn(mac_learning)
1185 1216
         extension_kwargs = {}
1217
+        if binding:
1218
+            extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
1186 1219
         if mac_learning:
1187 1220
             extension_kwargs['mac_learning_enabled'] = True
1188 1221
         api.neutron.port_update(IsA(http.HttpRequest), port.id,
@@ -1190,6 +1223,7 @@ class NetworkPortTests(test.BaseAdminViewTests):
1190 1223
                                 admin_state_up=port.admin_state_up,
1191 1224
                                 device_id=port.device_id,
1192 1225
                                 device_owner=port.device_owner,
1226
+                                binding__host_id=port.binding__host_id,
1193 1227
                                 **extension_kwargs)\
1194 1228
             .AndReturn(port)
1195 1229
         self.mox.ReplayAll()
@@ -1199,7 +1233,10 @@ class NetworkPortTests(test.BaseAdminViewTests):
1199 1233
                      'name': port.name,
1200 1234
                      'admin_state': port.admin_state_up,
1201 1235
                      'device_id': port.device_id,
1202
-                     'device_owner': port.device_owner}
1236
+                     'device_owner': port.device_owner,
1237
+                     'binding__host_id': port.binding__host_id}
1238
+        if binding:
1239
+            form_data['binding__vnic_type'] = port.binding__vnic_type
1203 1240
         if mac_learning:
1204 1241
             form_data['mac_state'] = True
1205 1242
         url = reverse('horizon:admin:networks:editport',
@@ -1220,16 +1257,22 @@ class NetworkPortTests(test.BaseAdminViewTests):
1220 1257
                                       'is_extension_supported',
1221 1258
                                       'port_update')})
1222 1259
     def test_port_update_post_exception_with_mac_learning(self):
1223
-        self._test_port_update_post_exception(mac_learning=True)
1260
+        self._test_port_update_post_exception(mac_learning=True, binding=False)
1224 1261
 
1225
-    def _test_port_update_post_exception(self, mac_learning=False):
1262
+    def _test_port_update_post_exception(self, mac_learning=False,
1263
+                                         binding=False):
1226 1264
         port = self.ports.first()
1227 1265
         api.neutron.port_get(IsA(http.HttpRequest), port.id)\
1228 1266
             .AndReturn(port)
1267
+        api.neutron.is_extension_supported(IsA(http.HttpRequest),
1268
+                                           'binding')\
1269
+            .AndReturn(binding)
1229 1270
         api.neutron.is_extension_supported(IsA(http.HttpRequest),
1230 1271
                                            'mac-learning')\
1231 1272
             .AndReturn(mac_learning)
1232 1273
         extension_kwargs = {}
1274
+        if binding:
1275
+            extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
1233 1276
         if mac_learning:
1234 1277
             extension_kwargs['mac_learning_enabled'] = True
1235 1278
         api.neutron.port_update(IsA(http.HttpRequest), port.id,
@@ -1237,6 +1280,7 @@ class NetworkPortTests(test.BaseAdminViewTests):
1237 1280
                                 admin_state_up=port.admin_state_up,
1238 1281
                                 device_id=port.device_id,
1239 1282
                                 device_owner=port.device_owner,
1283
+                                binding__host_id=port.binding__host_id,
1240 1284
                                 **extension_kwargs)\
1241 1285
             .AndRaise(self.exceptions.neutron)
1242 1286
         self.mox.ReplayAll()
@@ -1246,7 +1290,10 @@ class NetworkPortTests(test.BaseAdminViewTests):
1246 1290
                      'name': port.name,
1247 1291
                      'admin_state': port.admin_state_up,
1248 1292
                      'device_id': port.device_id,
1249
-                     'device_owner': port.device_owner}
1293
+                     'device_owner': port.device_owner,
1294
+                     'binding__host_id': port.binding__host_id}
1295
+        if binding:
1296
+            form_data['binding__vnic_type'] = port.binding__vnic_type
1250 1297
         if mac_learning:
1251 1298
             form_data['mac_state'] = True
1252 1299
         url = reverse('horizon:admin:networks:editport',

+ 29
- 2
openstack_dashboard/dashboards/project/networks/ports/forms.py View File

@@ -14,6 +14,7 @@
14 14
 
15 15
 import logging
16 16
 
17
+from django.conf import settings
17 18
 from django.core.urlresolvers import reverse
18 19
 from django.utils.translation import ugettext_lazy as _
19 20
 
@@ -25,6 +26,8 @@ from openstack_dashboard import api
25 26
 
26 27
 
27 28
 LOG = logging.getLogger(__name__)
29
+VNIC_TYPES = [('normal', _('Normal')), ('direct', _('Direct')),
30
+              ('macvtap', _('MacVTap'))]
28 31
 
29 32
 
30 33
 class UpdatePort(forms.SelfHandlingForm):
@@ -42,18 +45,42 @@ class UpdatePort(forms.SelfHandlingForm):
42 45
 
43 46
     def __init__(self, request, *args, **kwargs):
44 47
         super(UpdatePort, self).__init__(request, *args, **kwargs)
48
+
49
+        if api.neutron.is_extension_supported(request, 'binding'):
50
+            neutron_settings = getattr(settings,
51
+                                       'OPENSTACK_NEUTRON_NETWORK', {})
52
+            supported_vnic_types = neutron_settings.get(
53
+                'supported_vnic_types', ['*'])
54
+            if supported_vnic_types == ['*']:
55
+                vnic_type_choices = VNIC_TYPES
56
+            else:
57
+                vnic_type_choices = [
58
+                    vnic_type for vnic_type in VNIC_TYPES
59
+                    if vnic_type[0] in supported_vnic_types
60
+                ]
61
+
62
+            self.fields['binding__vnic_type'] = forms.ChoiceField(
63
+                choices=vnic_type_choices,
64
+                label=_("Binding: VNIC Type"),
65
+                help_text=_("The VNIC type that is bound to the neutron port"),
66
+                required=False)
67
+
45 68
         if api.neutron.is_extension_supported(request, 'mac-learning'):
46 69
             self.fields['mac_state'] = forms.BooleanField(
47
-                label=_("Mac Learning State"), required=False)
70
+                label=_("MAC Learning State"), initial=False, required=False)
48 71
 
49 72
     def handle(self, request, data):
50 73
         data['admin_state'] = (data['admin_state'] == 'True')
51 74
         try:
52 75
             LOG.debug('params = %s' % data)
53 76
             extension_kwargs = {}
77
+            if 'binding__vnic_type' in data:
78
+                extension_kwargs['binding__vnic_type'] = \
79
+                    data['binding__vnic_type']
54 80
             if 'mac_state' in data:
55 81
                 extension_kwargs['mac_learning_enabled'] = data['mac_state']
56
-            port = api.neutron.port_update(request, data['port_id'],
82
+            port = api.neutron.port_update(request,
83
+                                           data['port_id'],
57 84
                                            name=data['name'],
58 85
                                            admin_state_up=data['admin_state'],
59 86
                                            **extension_kwargs)

+ 4
- 5
openstack_dashboard/dashboards/project/networks/ports/views.py View File

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

+ 27
- 20
openstack_dashboard/dashboards/project/networks/templates/networks/ports/_detail_overview.html View File

@@ -1,11 +1,7 @@
1 1
 {% load i18n sizeformat %}
2 2
 {% load url from future %}
3 3
 
4
-<h3>{% trans "Port Overview" %}</h3>
5
-
6
-<div class="info row detail">
7
-  <h4>{% trans "Port" %}</h4>
8
-  <hr class="header_rule">
4
+<div class="detail">
9 5
   <dl class="dl-horizontal">
10 6
     <dt>{% trans "Name" %}</dt>
11 7
     <dd>{{ port.name|default:_("None") }}</dd>
@@ -16,18 +12,7 @@
16 12
     <dd><a href="{{ network_url }}">{{ port.network_id|default:_("None") }}</a></dd>
17 13
     <dt>{% trans "Project ID" %}</dt>
18 14
     <dd>{{ port.tenant_id|default:_("-") }}</dd>
19
-    <dt>{% trans "Fixed IP" %}</dt>
20
-    <dd>
21
-      {% if port.fixed_ips.items|length > 1 %}
22
-        {% for ip in port.fixed_ips %}
23
-          <b>{% trans "IP address:" %}</b> {{ ip.ip_address }},
24
-          <b>{% trans "Subnet ID" %}</b> {{ ip.subnet_id }}<br>
25
-        {% endfor %}
26
-      {% else %}
27
-        {% trans "None" %}
28
-      {% endif %}
29
-    </dd>
30
-    <dt>{% trans "Mac Address" %}</dt>
15
+    <dt>{% trans "MAC Address" %}</dt>
31 16
     <dd>{{ port.mac_address|default:_("None") }}</dd>
32 17
     <dt>{% trans "Status" %}</dt>
33 18
     <dd>{{ port.status_label|default:_("None") }}</dd>
@@ -37,12 +22,34 @@
37 22
     <dt>{% trans "MAC Learning State" %}</dt>
38 23
     <dd>{{ port.mac_state }}</dd>
39 24
     {% endif %}
40
-    <dt>{% trans "Attached Device" %}</dt>
25
+    <h4>{% trans "Fixed IP" %}</h4>
26
+    <hr class="header_rule">
27
+    {% if port.fixed_ips.items|length > 1 %}
28
+      {% for ip in port.fixed_ips %}
29
+      <dt>{% trans "IP Address" %}</dt>
30
+      <dd>{{ ip.ip_address }}</dd>
31
+      {% url 'horizon:project:networks:subnets:detail' ip.subnet_id as subnet_url %}
32
+      <dt>{% trans "Subnet ID" %}</dt>
33
+      <dd><a href="{{ subnet_url }}">{{ ip.subnet_id }}</a></dd>
34
+      {% endfor %}
35
+    {% else %}
36
+      <dd>{% trans "None" %}</dd>
37
+    {% endif %}
38
+    <h4>{% trans "Attached Device" %}</h4>
39
+    <hr class="header_rule">
41 40
     {% if port.device_id|length > 1 or port.device_owner %}
42
-      <dd><b>{% trans "Device Owner" %}</b>: {{ port.device_owner|default:_("None") }}</dd>
43
-      <dd><b>{% trans "Device ID" %}</b>: {{ port.device_id|default:_("-") }}</dd>
41
+      <dt>{% trans "Device Owner" %}</dt>
42
+      <dd>{{ port.device_owner|default:_("None") }}</dd>
43
+      <dt>{% trans "Device ID" %}</dt>
44
+      <dd>{{ port.device_id|default:_("None") }}</dd>
44 45
     {% else %}
45 46
       <dd>{% trans "No attached device" %}</dd>
46 47
     {% endif %}
48
+    <h4>{% trans "Binding" %}</h4>
49
+    <hr class="header_rule">
50
+    {% if port.binding__vnic_type %}
51
+      <dt>{% trans "VNIC Type" %}</dt>
52
+      <dd>{{ port.binding__vnic_type }}</dd>
53
+    {% endif %}
47 54
   </dl>
48 55
 </div>

+ 22
- 3
openstack_dashboard/dashboards/project/networks/tests.py View File

@@ -1691,11 +1691,14 @@ class NetworkPortTests(test.TestCase):
1691 1691
     def test_port_update_get_with_mac_learning(self):
1692 1692
         self._test_port_update_get(mac_learning=True)
1693 1693
 
1694
-    def _test_port_update_get(self, mac_learning=False):
1694
+    def _test_port_update_get(self, mac_learning=False, binding=False):
1695 1695
         port = self.ports.first()
1696 1696
         api.neutron.port_get(IsA(http.HttpRequest),
1697 1697
                              port.id)\
1698 1698
             .AndReturn(port)
1699
+        api.neutron.is_extension_supported(IsA(http.HttpRequest),
1700
+                                           'binding')\
1701
+            .AndReturn(binding)
1699 1702
         api.neutron.is_extension_supported(IsA(http.HttpRequest),
1700 1703
                                            'mac-learning')\
1701 1704
             .AndReturn(mac_learning)
@@ -1719,14 +1722,19 @@ class NetworkPortTests(test.TestCase):
1719 1722
     def test_port_update_post_with_mac_learning(self):
1720 1723
         self._test_port_update_post(mac_learning=True)
1721 1724
 
1722
-    def _test_port_update_post(self, mac_learning=False):
1725
+    def _test_port_update_post(self, mac_learning=False, binding=False):
1723 1726
         port = self.ports.first()
1724 1727
         api.neutron.port_get(IsA(http.HttpRequest), port.id)\
1725 1728
             .AndReturn(port)
1729
+        api.neutron.is_extension_supported(IsA(http.HttpRequest),
1730
+                                           'binding')\
1731
+            .AndReturn(binding)
1726 1732
         api.neutron.is_extension_supported(IsA(http.HttpRequest),
1727 1733
                                            'mac-learning')\
1728 1734
             .AndReturn(mac_learning)
1729 1735
         extension_kwargs = {}
1736
+        if binding:
1737
+            extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
1730 1738
         if mac_learning:
1731 1739
             extension_kwargs['mac_learning_enabled'] = True
1732 1740
         api.neutron.port_update(IsA(http.HttpRequest), port.id,
@@ -1740,6 +1748,8 @@ class NetworkPortTests(test.TestCase):
1740 1748
                      'port_id': port.id,
1741 1749
                      'name': port.name,
1742 1750
                      'admin_state': port.admin_state_up}
1751
+        if binding:
1752
+            form_data['binding__vnic_type'] = port.binding__vnic_type
1743 1753
         if mac_learning:
1744 1754
             form_data['mac_state'] = True
1745 1755
         url = reverse('horizon:project:networks:editport',
@@ -1762,14 +1772,21 @@ class NetworkPortTests(test.TestCase):
1762 1772
     def test_port_update_post_exception_with_mac_learning(self):
1763 1773
         self._test_port_update_post_exception(mac_learning=True)
1764 1774
 
1765
-    def _test_port_update_post_exception(self, mac_learning=False):
1775
+    def _test_port_update_post_exception(self, mac_learning=False,
1776
+                                         binding=False):
1777
+
1766 1778
         port = self.ports.first()
1767 1779
         api.neutron.port_get(IsA(http.HttpRequest), port.id)\
1768 1780
             .AndReturn(port)
1781
+        api.neutron.is_extension_supported(IsA(http.HttpRequest),
1782
+                                           'binding')\
1783
+            .AndReturn(binding)
1769 1784
         api.neutron.is_extension_supported(IsA(http.HttpRequest),
1770 1785
                                            'mac-learning')\
1771 1786
             .AndReturn(mac_learning)
1772 1787
         extension_kwargs = {}
1788
+        if binding:
1789
+            extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
1773 1790
         if mac_learning:
1774 1791
             extension_kwargs['mac_learning_enabled'] = True
1775 1792
         api.neutron.port_update(IsA(http.HttpRequest), port.id,
@@ -1783,6 +1800,8 @@ class NetworkPortTests(test.TestCase):
1783 1800
                      'port_id': port.id,
1784 1801
                      'name': port.name,
1785 1802
                      'admin_state': port.admin_state_up}
1803
+        if binding:
1804
+            form_data['binding__vnic_type'] = port.binding__vnic_type
1786 1805
         if mac_learning:
1787 1806
             form_data['mac_state'] = True
1788 1807
         url = reverse('horizon:project:networks:editport',

+ 6
- 0
openstack_dashboard/local/local_settings.py.example View File

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

+ 19
- 6
openstack_dashboard/test/test_data/neutron_data.py View File

@@ -161,7 +161,10 @@ def data(TEST):
161 161
                  'name': '',
162 162
                  'network_id': network_dict['id'],
163 163
                  'status': 'ACTIVE',
164
-                 'tenant_id': network_dict['tenant_id']}
164
+                 'tenant_id': network_dict['tenant_id'],
165
+                 'binding:vnic_type': 'normal',
166
+                 'binding:host_id': 'host'}
167
+
165 168
     TEST.api_ports.add(port_dict)
166 169
     TEST.ports.add(neutron.Port(port_dict))
167 170
 
@@ -175,7 +178,9 @@ def data(TEST):
175 178
                  'name': '',
176 179
                  'network_id': network_dict['id'],
177 180
                  'status': 'ACTIVE',
178
-                 'tenant_id': network_dict['tenant_id']}
181
+                 'tenant_id': network_dict['tenant_id'],
182
+                 'binding:vnic_type': 'normal',
183
+                 'binding:host_id': 'host'}
179 184
     TEST.api_ports.add(port_dict)
180 185
     TEST.ports.add(neutron.Port(port_dict))
181 186
     assoc_port = port_dict
@@ -190,7 +195,9 @@ def data(TEST):
190 195
                  'name': '',
191 196
                  'network_id': network_dict['id'],
192 197
                  'status': 'ACTIVE',
193
-                 'tenant_id': network_dict['tenant_id']}
198
+                 'tenant_id': network_dict['tenant_id'],
199
+                 'binding:vnic_type': 'normal',
200
+                 'binding:host_id': 'host'}
194 201
     TEST.api_ports.add(port_dict)
195 202
     TEST.ports.add(neutron.Port(port_dict))
196 203
 
@@ -238,7 +245,9 @@ def data(TEST):
238 245
                  'name': '',
239 246
                  'network_id': network_dict['id'],
240 247
                  'status': 'ACTIVE',
241
-                 'tenant_id': network_dict['tenant_id']}
248
+                 'tenant_id': network_dict['tenant_id'],
249
+                 'binding:vnic_type': 'normal',
250
+                 'binding:host_id': 'host'}
242 251
 
243 252
     TEST.api_ports.add(port_dict)
244 253
     TEST.ports.add(neutron.Port(port_dict))
@@ -350,7 +359,9 @@ def data(TEST):
350 359
                  'name': '',
351 360
                  'network_id': TEST.networks.get(name="ext_net")['id'],
352 361
                  'status': 'ACTIVE',
353
-                 'tenant_id': '1'}
362
+                 'tenant_id': '1',
363
+                 'binding:vnic_type': 'normal',
364
+                 'binding:host_id': 'host'}
354 365
     TEST.api_ports.add(port_dict)
355 366
     TEST.ports.add(neutron.Port(port_dict))
356 367
 
@@ -1142,6 +1153,8 @@ def data(TEST):
1142 1153
                  'name': 'port5',
1143 1154
                  'network_id': TEST.networks.get(name="net4")['id'],
1144 1155
                  'status': 'ACTIVE',
1145
-                 'tenant_id': TEST.networks.get(name="net4")['tenant_id']}
1156
+                 'tenant_id': TEST.networks.get(name="net4")['tenant_id'],
1157
+                 'binding:vnic_type': 'normal',
1158
+                 'binding:host_id': 'host'}
1146 1159
     TEST.api_ports.add(port_dict)
1147 1160
     TEST.ports.add(neutron.Port(port_dict))

Loading…
Cancel
Save