This patch adds support for requested vnic_type to be plugged to neutron port to ML2 plugin.
This patch contains:
1. New attribute 'binding:vnic_type' added to port binding extension.
Possible values are 'direct', 'macvtap' and 'normal'.
'binding:vnic_type' is allowed to be defined on port creation or changed
on port update by admin or tenant user.
'binding:vnic_type' can be also skipped in port defintion
2. Management of vnic_type by ML2 plugin, assuming default
vnic_type=normal
3. Add 'vnic_type' to ml2_port_bindings DB table
4. Add supported vnic_types for MechanismDrivers that are capable to bind
port.
5. Add DB migration script for ml2_vnic_type.
DocImpact: Need to update portbindings API docs and include in SR-IOV user docs
Change-Id: Ic88708fa9ece742f807c1d09bb49e499f99bd092
Implements: blueprint ml2-request-vnic-type
"create_port:port_security_enabled": "rule:admin_or_network_owner",
"create_port:binding:host_id": "rule:admin_only",
"create_port:binding:profile": "rule:admin_only",
+ "create_port:binding:vnic_type": "rule:admin_or_owner",
"create_port:mac_learning_enabled": "rule:admin_or_network_owner",
"get_port": "rule:admin_or_owner",
"get_port:queue_id": "rule:admin_only",
"get_port:binding:capabilities": "rule:admin_only",
"get_port:binding:host_id": "rule:admin_only",
"get_port:binding:profile": "rule:admin_only",
+ "get_port:binding:vnic_type": "rule:admin_or_owner",
"update_port": "rule:admin_or_owner",
"update_port:fixed_ips": "rule:admin_or_network_owner",
"update_port:port_security_enabled": "rule:admin_or_network_owner",
"update_port:binding:host_id": "rule:admin_only",
"update_port:binding:profile": "rule:admin_only",
+ "update_port:binding:vnic_type": "rule:admin_or_owner",
"update_port:mac_learning_enabled": "rule:admin_or_network_owner",
"delete_port": "rule:admin_or_owner",
--- /dev/null
+# Copyright 2014 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+"""ml2_vnic_type
+
+Revision ID: 27cc183af192
+Revises: 4ca36cfc898c
+Create Date: 2014-02-09 12:19:21.362967
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '27cc183af192'
+down_revision = '4ca36cfc898c'
+
+# Change to ['*'] if this migration applies to all plugins
+
+migration_for_plugins = [
+ 'neutron.plugins.ml2.plugin.Ml2Plugin'
+]
+
+from alembic import op
+import sqlalchemy as sa
+
+from neutron.db import migration
+from neutron.extensions import portbindings
+
+
+def upgrade(active_plugins=None, options=None):
+ if not migration.should_run(active_plugins, migration_for_plugins):
+ return
+
+ op.add_column('ml2_port_bindings',
+ sa.Column('vnic_type', sa.String(length=64),
+ nullable=False,
+ server_default=portbindings.VNIC_NORMAL))
+
+
+def downgrade(active_plugins=None, options=None):
+ if not migration.should_run(active_plugins, migration_for_plugins):
+ return
+
+ op.drop_column('ml2_port_bindings', 'vnic_type')
binding_profile_set = attributes.is_attr_set(binding_profile)
if not binding_profile_set and binding_profile is not None:
del port[portbindings.PROFILE]
+
+ binding_vnic = port.get(portbindings.VNIC_TYPE)
+ binding_vnic_set = attributes.is_attr_set(binding_vnic)
+ if not binding_vnic_set and binding_vnic is not None:
+ del port[portbindings.VNIC_TYPE]
+ # REVISIT(irenab) Add support for vnic_type for plugins that
+ # can handle more than one type.
+ # Currently implemented for ML2 plugin that does not use
+ # PortBindingMixin.
+
host = port_data.get(portbindings.HOST_ID)
host_set = attributes.is_attr_set(host)
with context.session.begin(subtransactions=True):
from neutron.api import extensions
from neutron.api.v2 import attributes
-
+# The type of vnic that this port should be attached to
+VNIC_TYPE = 'binding:vnic_type'
# The service will return the vif type for the specific port.
VIF_TYPE = 'binding:vif_type'
# In some cases different implementations may be run on different hosts.
VIF_TYPE_802_QBH, VIF_TYPE_HYPERV, VIF_TYPE_MIDONET,
VIF_TYPE_OTHER]
+VNIC_NORMAL = 'normal'
+VNIC_DIRECT = 'direct'
+VNIC_MACVTAP = 'macvtap'
+VNIC_TYPES = [VNIC_NORMAL, VNIC_DIRECT, VNIC_MACVTAP]
EXTENDED_ATTRIBUTES_2_0 = {
'ports': {
'default': attributes.ATTR_NOT_SPECIFIED,
'enforce_policy': True,
'is_visible': True},
+ VNIC_TYPE: {'allow_post': True, 'allow_put': True,
+ 'default': VNIC_NORMAL,
+ 'is_visible': True,
+ 'validate': {'type:values': VNIC_TYPES},
+ 'enforce_policy': True},
HOST_ID: {'allow_post': True, 'allow_put': True,
'default': attributes.ATTR_NOT_SPECIFIED,
'is_visible': True,
import six
+from neutron.extensions import portbindings
from neutron.openstack.common import log
from neutron.plugins.ml2 import driver_api as api
check_segment_for_agent().
"""
- def __init__(self, agent_type, vif_type, cap_port_filter):
+ def __init__(self, agent_type, vif_type, cap_port_filter,
+ supported_vnic_types=[portbindings.VNIC_NORMAL]):
"""Initialize base class for specific L2 agent type.
:param agent_type: Constant identifying agent type in agents_db
self.agent_type = agent_type
self.vif_type = vif_type
self.cap_port_filter = cap_port_filter
+ self.supported_vnic_types = supported_vnic_types
def initialize(self):
pass
"network %(network)s"),
{'port': context.current['id'],
'network': context.network.current['id']})
+ vnic_type = context.current.get(portbindings.VNIC_TYPE,
+ portbindings.VNIC_NORMAL)
+ if vnic_type not in self.supported_vnic_types:
+ LOG.debug(_("Refusing to bind due to unsupported vnic_type: %s"),
+ vnic_type)
+ return
for agent in context.host_agents(self.agent_type):
LOG.debug(_("Checking agent: %s"), agent)
if agent['alive']:
attempt to establish a port binding.
"""
binding = context._binding
- LOG.debug(_("Attempting to bind port %(port)s on host %(host)s"),
+ LOG.debug(_("Attempting to bind port %(port)s on host %(host)s "
+ "for vnic_type %(vnic_type)s"),
{'port': context._port['id'],
- 'host': binding.host})
+ 'host': binding.host,
+ 'vnic_type': binding.vnic_type})
for driver in self.ordered_mech_drivers:
try:
driver.obj.bind_port(context)
if binding.segment:
binding.driver = driver.name
LOG.debug(_("Bound port: %(port)s, host: %(host)s, "
+ "vnic_type: %(vnic_type)s, "
"driver: %(driver)s, vif_type: %(vif_type)s, "
"cap_port_filter: %(cap_port_filter)s, "
"segment: %(segment)s"),
'host': binding.host,
'driver': binding.driver,
'vif_type': binding.vif_type,
+ 'vnic_type': binding.vnic_type,
'cap_port_filter': binding.cap_port_filter,
'segment': binding.segment})
return
from neutron.db import model_base
from neutron.db import models_v2
+from neutron.extensions import portbindings
class NetworkSegment(model_base.BASEV2, models_v2.HasId):
sa.ForeignKey('ports.id', ondelete="CASCADE"),
primary_key=True)
host = sa.Column(sa.String(255), nullable=False)
+ vnic_type = sa.Column(sa.String(64), nullable=False,
+ default=portbindings.VNIC_NORMAL)
vif_type = sa.Column(sa.String(64), nullable=False)
cap_port_filter = sa.Column(sa.Boolean, nullable=False)
driver = sa.Column(sa.String(64))
self._update_port_dict_binding(port, binding)
host = attrs and attrs.get(portbindings.HOST_ID)
host_set = attributes.is_attr_set(host)
+ vnic_type = attrs and attrs.get(portbindings.VNIC_TYPE)
+ vnic_type_set = attributes.is_attr_set(vnic_type)
if binding.vif_type != portbindings.VIF_TYPE_UNBOUND:
- if (not host_set and binding.segment and
+ if (not host_set and not vnic_type_set and binding.segment and
self.mechanism_manager.validate_port_binding(mech_context)):
return False
self.mechanism_manager.unbind_port(mech_context)
self._update_port_dict_binding(port, binding)
# Return True only if an agent notification is needed.
- # This will happen if a new host was specified and that host
+ # This will happen if a new host or vnic_type was specified that
# differs from the current one. Note that host_set is True
# even if the host is an empty string
- ret_value = host_set and binding.get('host') != host
+ ret_value = ((host_set and binding.get('host') != host) or
+ (vnic_type_set and binding.get('vnic_type') != vnic_type))
if host_set:
binding.host = host
port[portbindings.HOST_ID] = host
+ if vnic_type_set:
+ binding.vnic_type = vnic_type
+ port[portbindings.VNIC_TYPE] = vnic_type
+
if binding.host:
self.mechanism_manager.bind_port(mech_context)
self._update_port_dict_binding(port, binding)
def _update_port_dict_binding(self, port, binding):
port[portbindings.HOST_ID] = binding.host
+ port[portbindings.VNIC_TYPE] = binding.vnic_type
port[portbindings.VIF_TYPE] = binding.vif_type
port[portbindings.CAPABILITIES] = {
portbindings.CAP_PORT_FILTER: binding.cap_port_filter}
#
import contextlib
+import httplib
from oslo.config import cfg
from webob import exc
self._test_list_resources(
'port', (port1, port3),
query_params='%s=%s' % (portbindings.HOST_ID, self.hostname))
+
+
+class PortBindingsVnicTestCaseMixin(object):
+ fmt = 'json'
+ vnic_type = portbindings.VNIC_NORMAL
+
+ def _check_response_portbindings_vnic_type(self, port):
+ self.assertIn('status', port)
+ self.assertEqual(port[portbindings.VNIC_TYPE], self.vnic_type)
+
+ def test_port_vnic_type_non_admin(self):
+ with self.network(set_context=True,
+ tenant_id='test') as net1:
+ with self.subnet(network=net1) as subnet1:
+ vnic_arg = {portbindings.VNIC_TYPE: self.vnic_type}
+ with self.port(subnet=subnet1,
+ expected_res_status=httplib.CREATED,
+ arg_list=(portbindings.VNIC_TYPE,),
+ set_context=True,
+ tenant_id='test',
+ **vnic_arg) as port:
+ # Check a response of create_port
+ self._check_response_portbindings_vnic_type(port['port'])
+
+ def test_port_vnic_type(self):
+ vnic_arg = {portbindings.VNIC_TYPE: self.vnic_type}
+ with self.port(name='name', arg_list=(portbindings.VNIC_TYPE,),
+ **vnic_arg) as port:
+ port_id = port['port']['id']
+ # Check a response of create_port
+ self._check_response_portbindings_vnic_type(port['port'])
+ # Check a response of get_port
+ ctx = context.get_admin_context()
+ port = self._show('ports', port_id, neutron_context=ctx)['port']
+ self._check_response_portbindings_vnic_type(port)
+ # By default user is admin - now test non admin user
+ ctx = context.Context(user_id=None,
+ tenant_id=self._tenant_id,
+ is_admin=False,
+ read_deleted="no")
+ non_admin_port = self._show(
+ 'ports', port_id, neutron_context=ctx)['port']
+ self._check_response_portbindings_vnic_type(non_admin_port)
+
+ def test_ports_vnic_type(self):
+ cfg.CONF.set_default('allow_overlapping_ips', True)
+ vnic_arg = {portbindings.VNIC_TYPE: self.vnic_type}
+ with contextlib.nested(
+ self.port(name='name1',
+ arg_list=(portbindings.VNIC_TYPE,),
+ **vnic_arg),
+ self.port(name='name2')):
+ ctx = context.get_admin_context()
+ ports = self._list('ports', neutron_context=ctx)['ports']
+ self.assertEqual(2, len(ports))
+ for port in ports:
+ if port['name'] == 'name1':
+ self._check_response_portbindings_vnic_type(port)
+ else:
+ self.assertEqual(portbindings.VNIC_NORMAL,
+ port[portbindings.VNIC_TYPE])
+ # By default user is admin - now test non admin user
+ ctx = context.Context(user_id=None,
+ tenant_id=self._tenant_id,
+ is_admin=False,
+ read_deleted="no")
+ ports = self._list('ports', neutron_context=ctx)['ports']
+ self.assertEqual(2, len(ports))
+ for non_admin_port in ports:
+ self._check_response_portbindings_vnic_type(non_admin_port)
+
+ def test_ports_vnic_type_list(self):
+ cfg.CONF.set_default('allow_overlapping_ips', True)
+ vnic_arg = {portbindings.VNIC_TYPE: self.vnic_type}
+ with contextlib.nested(
+ self.port(name='name1',
+ arg_list=(portbindings.VNIC_TYPE,),
+ **vnic_arg),
+ self.port(name='name2'),
+ self.port(name='name3',
+ arg_list=(portbindings.VNIC_TYPE,),
+ **vnic_arg),) as (port1, port2, port3):
+ self._test_list_resources(
+ 'port', (port1, port2, port3),
+ query_params='%s=%s' % (portbindings.VNIC_TYPE,
+ self.vnic_type))
# under the License.
+from neutron.extensions import portbindings
from neutron.plugins.ml2 import driver_api as api
from neutron.tests import base
self._network_context = FakeNetworkContext(segments)
self._bound_segment_id = None
self._bound_vif_type = None
+ self._bound_vnic_type = portbindings.VNIC_NORMAL
self._bound_cap_port_filter = None
@property
pass
+class TestMl2PortBindingVnicType(Ml2PluginV2TestCase,
+ test_bindings.PortBindingsVnicTestCaseMixin):
+ pass
+
+
class TestMultiSegmentNetworks(Ml2PluginV2TestCase):
def setUp(self, plugin=None):