Merge "NSX|V: Add support for 'direct' vnic types"

This commit is contained in:
Jenkins 2017-02-26 15:33:02 +00:00 committed by Gerrit Code Review
commit aa637fefef
6 changed files with 227 additions and 9 deletions

View File

@ -1 +1 @@
01a33f93f5fd
e4c503f4133f

View File

@ -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'))

View File

@ -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)

View File

@ -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'))

View File

@ -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]}}

View File

@ -1534,6 +1534,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):