NSX|V: Add support for 'direct' vnic types
The NSX|V will support a direct vnic type iff this port meets the following criteria: 1. no security groups 2. no port security 3. is on a VLAN/FLAT network The reason for this is that the direct is only support via the DVS and there is no support for security groups and port security. Change-Id: Iff4cc72e724d40feff2b26fc4f24596cae3a749a
This commit is contained in:
parent
a2bf45ad60
commit
c615e8ce0e
@ -1 +1 @@
|
||||
01a33f93f5fd
|
||||
e4c503f4133f
|
||||
|
@ -0,0 +1,41 @@
|
||||
# Copyright 2017 VMware, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Port vnic_type support
|
||||
|
||||
Revision ID: e4c503f4133f
|
||||
Revises: 01a33f93f5fd
|
||||
Create Date: 2017-02-20 00:05:30.894680
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'e4c503f4133f'
|
||||
down_revision = '01a33f93f5fd'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table(
|
||||
'nsxv_port_ext_attributes',
|
||||
sa.Column('port_id', sa.String(length=36), nullable=False),
|
||||
sa.Column('vnic_type', sa.String(length=64), nullable=False,
|
||||
server_default='normal'),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['port_id'], ['ports.id'],
|
||||
ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('port_id'))
|
@ -18,6 +18,7 @@ import neutron.db.api as db
|
||||
from neutron.plugins.common import constants as neutron_const
|
||||
|
||||
import decorator
|
||||
from neutron_lib.api.definitions import portbindings as pbin
|
||||
from oslo_db import exception as db_exc
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
@ -870,3 +871,26 @@ def update_nsxv_subnet_ext_attributes(session, subnet_id,
|
||||
binding[ext_dns_search_domain.DNS_SEARCH_DOMAIN] = dns_search_domain
|
||||
binding[ext_dhcp_mtu.DHCP_MTU] = dhcp_mtu
|
||||
return binding
|
||||
|
||||
|
||||
def add_nsxv_port_ext_attributes(session, port_id,
|
||||
vnic_type=pbin.VNIC_NORMAL):
|
||||
with session.begin(subtransactions=True):
|
||||
binding = nsxv_models.NsxvPortExtAttributes(
|
||||
port_id=port_id,
|
||||
vnic_type=vnic_type)
|
||||
session.add(binding)
|
||||
return binding
|
||||
|
||||
|
||||
def update_nsxv_port_ext_attributes(session, port_id,
|
||||
vnic_type=pbin.VNIC_NORMAL):
|
||||
try:
|
||||
binding = session.query(
|
||||
nsxv_models.NsxvPortExtAttributes).filter_by(
|
||||
port_id=port_id).one()
|
||||
binding['vnic_type'] = vnic_type
|
||||
return binding
|
||||
except exc.NoResultFound:
|
||||
return add_nsxv_port_ext_attributes(
|
||||
session, port_id, vnic_type=vnic_type)
|
||||
|
@ -20,6 +20,7 @@ from sqlalchemy import orm
|
||||
|
||||
from neutron.db import l3_db
|
||||
from neutron.db import models_v2
|
||||
from neutron.extensions import portbindings
|
||||
from oslo_db.sqlalchemy import models
|
||||
|
||||
from vmware_nsx.common import nsxv_constants
|
||||
@ -361,3 +362,22 @@ class NsxvSubnetExtAttributes(model_base.BASEV2, models.TimestampMixin):
|
||||
models_v2.Subnet,
|
||||
backref=orm.backref("nsxv_subnet_attributes", lazy='joined',
|
||||
uselist=False, cascade='delete'))
|
||||
|
||||
|
||||
class NsxvPortExtAttributes(model_base.BASEV2, models.TimestampMixin):
|
||||
"""Port attributes managed by NSX plugin extensions."""
|
||||
|
||||
__tablename__ = 'nsxv_port_ext_attributes'
|
||||
|
||||
port_id = sa.Column(sa.String(36),
|
||||
sa.ForeignKey('ports.id', ondelete="CASCADE"),
|
||||
primary_key=True)
|
||||
vnic_type = sa.Column(sa.String(64), nullable=False,
|
||||
default=portbindings.VNIC_NORMAL,
|
||||
server_default=portbindings.VNIC_NORMAL)
|
||||
# Add a relationship to the port model in order to instruct
|
||||
# SQLAlchemy to eagerly load this association
|
||||
port = orm.relationship(
|
||||
models_v2.Port,
|
||||
backref=orm.backref("nsx_port_attributes", lazy='joined',
|
||||
uselist=False, cascade='delete'))
|
||||
|
@ -209,13 +209,6 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
||||
neutron_extensions.append_api_extensions_path(
|
||||
[vmware_nsx.NSX_EXT_PATH])
|
||||
|
||||
self.base_binding_dict = {
|
||||
pbin.VNIC_TYPE: pbin.VNIC_NORMAL,
|
||||
pbin.VIF_TYPE: nsx_constants.VIF_TYPE_DVS,
|
||||
pbin.VIF_DETAILS: {
|
||||
# TODO(rkukura): Replace with new VIF security details
|
||||
pbin.CAP_PORT_FILTER:
|
||||
'security-group' in self.supported_extension_aliases}}
|
||||
# This needs to be set prior to binding callbacks
|
||||
if cfg.CONF.nsxv.use_dvs_features:
|
||||
self._vcm = dvs.VCManager()
|
||||
@ -402,6 +395,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
||||
with db_api.context_manager.writer.using(ctx):
|
||||
self._extension_manager.extend_port_dict(
|
||||
ctx.session, portdb, result)
|
||||
self._extend_port_dict_binding(portdb,
|
||||
result)
|
||||
|
||||
def _ext_extend_subnet_dict(self, result, subnetdb):
|
||||
ctx = n_context.get_admin_context()
|
||||
@ -687,6 +682,21 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
||||
# TODO(salvatore-orlando): Validate tranport zone uuid
|
||||
# which should be specified in physical_network
|
||||
|
||||
def _validate_network_type(self, context, network_id, net_types):
|
||||
bindings = nsxv_db.get_network_bindings(context.session,
|
||||
network_id)
|
||||
multiprovider = nsx_db.is_multiprovider_network(context.session,
|
||||
network_id)
|
||||
if bindings:
|
||||
if not multiprovider:
|
||||
return bindings[0].binding_type in net_types
|
||||
else:
|
||||
for binding in bindings:
|
||||
if binding.binding_type not in net_types:
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
|
||||
def _extend_network_dict_provider(self, context, network,
|
||||
multiprovider=None, bindings=None):
|
||||
if not bindings:
|
||||
@ -1602,6 +1612,33 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
||||
context, port['port'], created_port)
|
||||
return created_port
|
||||
|
||||
def _process_vnic_type(self, context, port_data, attrs,
|
||||
has_security_groups, port_security):
|
||||
vnic_type = attrs and attrs.get(pbin.VNIC_TYPE)
|
||||
if attrs and validators.is_attr_set(vnic_type):
|
||||
if vnic_type == pbin.VNIC_NORMAL:
|
||||
pass
|
||||
elif vnic_type == pbin.VNIC_DIRECT:
|
||||
if has_security_groups or port_security:
|
||||
err_msg = _("Direct VNIC type requires no port "
|
||||
"security and no security groups!")
|
||||
raise n_exc.InvalidInput(error_message=err_msg)
|
||||
if not self._validate_network_type(
|
||||
context, port_data['network_id'],
|
||||
[c_utils.NsxVNetworkTypes.VLAN,
|
||||
c_utils.NsxVNetworkTypes.FLAT,
|
||||
c_utils.NsxVNetworkTypes.PORTGROUP]):
|
||||
err_msg = _("Direct VNIC type requires VLAN, Flat or "
|
||||
"Portgroup network!")
|
||||
raise n_exc.InvalidInput(error_message=err_msg)
|
||||
else:
|
||||
err_msg = _("Only direct or normal VNIC types supported")
|
||||
raise n_exc.InvalidInput(error_message=err_msg)
|
||||
nsxv_db.update_nsxv_port_ext_attributes(
|
||||
session=context.session,
|
||||
port_id=port_data['id'],
|
||||
vnic_type=vnic_type)
|
||||
|
||||
def create_port(self, context, port):
|
||||
port_data = port['port']
|
||||
with context.session.begin(subtransactions=True):
|
||||
@ -1663,6 +1700,10 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
||||
context, neutron_db,
|
||||
attrs.get(addr_pair.ADDRESS_PAIRS)))
|
||||
|
||||
self._process_vnic_type(context, port_data, attrs,
|
||||
has_security_groups,
|
||||
port_security)
|
||||
|
||||
try:
|
||||
# Configure NSX - this should not be done in the DB transaction
|
||||
# Configure the DHCP Edge service
|
||||
@ -1781,7 +1822,6 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
||||
self._validate_address_pairs(attrs, original_port)
|
||||
orig_has_port_security = (cfg.CONF.nsxv.spoofguard_enabled and
|
||||
original_port[psec.PORTSECURITY])
|
||||
|
||||
port_ip_change = port_data.get('fixed_ips') is not None
|
||||
device_owner_change = port_data.get('device_owner') is not None
|
||||
# We do not support updating the port ip and device owner together
|
||||
@ -1888,6 +1928,10 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
||||
update_assigned_addresses = self.update_address_pairs_on_port(
|
||||
context, id, port, original_port, ret_port)
|
||||
|
||||
self._process_vnic_type(context, ret_port, attrs,
|
||||
has_security_groups,
|
||||
has_port_security)
|
||||
|
||||
if comp_owner_update:
|
||||
# Create dhcp bindings, the port is now owned by an instance
|
||||
self._create_dhcp_static_binding(context, ret_port)
|
||||
@ -2110,6 +2154,18 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
||||
|
||||
self._delete_dhcp_static_binding(context, neutron_db_port)
|
||||
|
||||
def _extend_port_dict_binding(self, portdb, result):
|
||||
result[pbin.VIF_TYPE] = nsx_constants.VIF_TYPE_DVS
|
||||
port_attr = portdb.get('nsx_port_attributes')
|
||||
if port_attr:
|
||||
result[pbin.VNIC_TYPE] = port_attr.vnic_type
|
||||
else:
|
||||
result[pbin.VNIC_TYPE] = pbin.VNIC_NORMAL
|
||||
result[pbin.VIF_DETAILS] = {
|
||||
# TODO(rkukura): Replace with new VIF security details
|
||||
pbin.CAP_PORT_FILTER:
|
||||
'security-group' in self.supported_extension_aliases}
|
||||
|
||||
def delete_subnet(self, context, id):
|
||||
subnet = self._get_subnet(context, id)
|
||||
filters = {'fixed_ips': {'subnet_id': [id]}}
|
||||
|
@ -1537,6 +1537,83 @@ class TestPortsV2(NsxVPluginV2TestCase,
|
||||
port2 = self.deserialize('json', res)
|
||||
self.assertEqual("MacAddressInUse", port2['NeutronError']['type'])
|
||||
|
||||
def _test_create_direct_network(self, vlan_id=0):
|
||||
net_type = vlan_id and 'vlan' or 'flat'
|
||||
name = 'direct_net'
|
||||
providernet_args = {pnet.NETWORK_TYPE: net_type,
|
||||
pnet.PHYSICAL_NETWORK: 'tzuuid'}
|
||||
if vlan_id:
|
||||
providernet_args[pnet.SEGMENTATION_ID] = vlan_id
|
||||
return self.network(name=name,
|
||||
providernet_args=providernet_args,
|
||||
arg_list=(pnet.NETWORK_TYPE,
|
||||
pnet.PHYSICAL_NETWORK,
|
||||
pnet.SEGMENTATION_ID))
|
||||
|
||||
def test_create_port_vnic_direct(self):
|
||||
with self._test_create_direct_network(vlan_id=7) as network:
|
||||
# Check that port security conflicts
|
||||
kwargs = {'binding:vnic_type': 'direct'}
|
||||
net_id = network['network']['id']
|
||||
res = self._create_port(self.fmt, net_id=net_id,
|
||||
arg_list=(portbindings.VNIC_TYPE,),
|
||||
**kwargs)
|
||||
self.assertEqual(res.status_int, webob.exc.HTTPBadRequest.code)
|
||||
|
||||
# Check that security group conflicts
|
||||
kwargs = {'binding:vnic_type': 'direct',
|
||||
'security_groups':
|
||||
['4cd70774-cc67-4a87-9b39-7d1db38eb087'],
|
||||
'port_security_enabled': False}
|
||||
net_id = network['network']['id']
|
||||
res = self._create_port(self.fmt, net_id=net_id,
|
||||
arg_list=(portbindings.VNIC_TYPE,
|
||||
'port_security_enabled'),
|
||||
**kwargs)
|
||||
self.assertEqual(res.status_int, webob.exc.HTTPBadRequest.code)
|
||||
|
||||
# All is kosher so we can create the port
|
||||
kwargs = {'binding:vnic_type': 'direct',
|
||||
'port_security_enabled': False}
|
||||
net_id = network['network']['id']
|
||||
res = self._create_port(self.fmt, net_id=net_id,
|
||||
arg_list=(portbindings.VNIC_TYPE,
|
||||
'port_security_enabled'),
|
||||
**kwargs)
|
||||
port = self.deserialize('json', res)
|
||||
self.assertEqual("direct", port['port']['binding:vnic_type'])
|
||||
|
||||
def test_create_port_vnic_direct_invalid_network(self):
|
||||
with self.network(name='not vlan/flat') as net:
|
||||
kwargs = {'binding:vnic_type': 'direct',
|
||||
'port_security_enabled': False}
|
||||
net_id = net['network']['id']
|
||||
res = self._create_port(self.fmt, net_id=net_id,
|
||||
arg_list=(portbindings.VNIC_TYPE,
|
||||
'port_security_enabled'),
|
||||
**kwargs)
|
||||
self.assertEqual(res.status_int, webob.exc.HTTPBadRequest.code)
|
||||
|
||||
def test_update_vnic_direct(self):
|
||||
with self._test_create_direct_network(vlan_id=7) as network:
|
||||
with self.subnet(network=network) as subnet:
|
||||
with self.port(subnet=subnet) as port:
|
||||
# need to do two updates as the update for port security
|
||||
# disabled requires that it can only change 2 items
|
||||
data = {'port': {'port_security_enabled': False,
|
||||
'security_groups': []}}
|
||||
req = self.new_update_request('ports',
|
||||
data, port['port']['id'])
|
||||
res = self.deserialize('json', req.get_response(self.api))
|
||||
self.assertEqual('normal',
|
||||
res['port']['binding:vnic_type'])
|
||||
data = {'port': {'binding:vnic_type': 'direct'}}
|
||||
req = self.new_update_request('ports',
|
||||
data, port['port']['id'])
|
||||
res = self.deserialize('json', req.get_response(self.api))
|
||||
self.assertEqual('direct',
|
||||
res['port']['binding:vnic_type'])
|
||||
|
||||
|
||||
class TestSubnetsV2(NsxVPluginV2TestCase,
|
||||
test_plugin.TestSubnetsV2):
|
||||
|
Loading…
Reference in New Issue
Block a user