Add trusted vif api extension for the port
This patch adds implementation of the "port_trusted_vif" API extension as ml2 extension. With this extension enabled, it is now possible for ADMIN users to set port as trusted without modifying directly 'binding:profile' field which is supposed to be just for machine to machine communication. Value set in the 'trusted' attribute of the port is included in the port's binding:profile so that it is still in the same place where e.g. Nova expects it. For now setting this flag directly in the port's binding:profile field is not forbidden and only warning is generated in such case but in future releases it should be forbiden and only allowed to be done using this new attribute of the port resource. This patch implements also definition of the new API extension directly in Neutron. It is temporary and will be removed once patch [1] in neutron-lib will be merged and released. [1] https://review.opendev.org/c/openstack/neutron-lib/+/923860 Closes-Bug: #2060916 Change-Id: I69785c5d72a5dc659c5a2f27e043c686790b4d2b
This commit is contained in:
parent
d73cc2eff6
commit
104cbf9e60
3
devstack/lib/port_trusted_vif
Normal file
3
devstack/lib/port_trusted_vif
Normal file
@ -0,0 +1,3 @@
|
||||
function configure_port_trusted_ml2_extension {
|
||||
neutron_ml2_extension_driver_add "port_trusted"
|
||||
}
|
@ -18,6 +18,7 @@ source $LIBDIR/tag_ports_during_bulk_creation
|
||||
source $LIBDIR/octavia
|
||||
source $LIBDIR/loki
|
||||
source $LIBDIR/local_ip
|
||||
source $LIBDIR/port_trusted_vif
|
||||
|
||||
# source the OVS/OVN compilation helper methods
|
||||
source $TOP_DIR/lib/neutron_plugins/ovs_source
|
||||
@ -98,6 +99,9 @@ if [[ "$1" == "stack" ]]; then
|
||||
fi
|
||||
configure_l3_agent
|
||||
fi
|
||||
if is_service_enabled q-port-trusted-vif neutron-port-trusted-vif; then
|
||||
configure_port_trusted_ml2_extension
|
||||
fi
|
||||
if [ $NEUTRON_CORE_PLUGIN = ml2 ]; then
|
||||
configure_ml2_extension_drivers
|
||||
fi
|
||||
|
@ -96,6 +96,7 @@ from neutron_lib.api.definitions import vpn
|
||||
from neutron_lib.api.definitions import vpn_endpoint_groups
|
||||
from neutron_lib import constants
|
||||
|
||||
from neutron.extensions import port_trusted_vif
|
||||
from neutron.extensions import quotasv2_detail
|
||||
from neutron.extensions import security_groups_default_rules
|
||||
|
||||
@ -158,6 +159,7 @@ ML2_SUPPORTED_API_EXTENSIONS = [
|
||||
port_numa_affinity_policy.ALIAS,
|
||||
port_numa_affinity_policy_socket.ALIAS,
|
||||
port_security.ALIAS,
|
||||
port_trusted_vif.ALIAS,
|
||||
provider_net.ALIAS,
|
||||
port_resource_request.ALIAS,
|
||||
qos.ALIAS,
|
||||
|
@ -297,6 +297,15 @@ rules = [
|
||||
),
|
||||
operations=ACTION_POST,
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='create_port:trusted',
|
||||
check_str=base.ADMIN,
|
||||
scope_types=['project'],
|
||||
description=(
|
||||
'Specify ``trusted`` attribute when creating a port'
|
||||
),
|
||||
operations=ACTION_POST,
|
||||
),
|
||||
|
||||
policy.DocumentedRuleDefault(
|
||||
name='get_port',
|
||||
@ -383,6 +392,13 @@ rules = [
|
||||
description='Get ``hints`` attribute of a port',
|
||||
operations=ACTION_GET,
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='get_port:trusted',
|
||||
check_str=base.ADMIN,
|
||||
scope_types=['project'],
|
||||
description='Get ``trusted`` attribute of a port',
|
||||
operations=ACTION_GET,
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='get_ports_tags',
|
||||
check_str=neutron_policy.policy_or(
|
||||
@ -641,6 +657,13 @@ rules = [
|
||||
description='Update ``hints`` attribute of a port',
|
||||
operations=ACTION_PUT,
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='update_port:trusted',
|
||||
check_str=base.ADMIN,
|
||||
scope_types=['project'],
|
||||
description='Update ``trusted`` attribute of a port',
|
||||
operations=ACTION_PUT,
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='update_ports_tags',
|
||||
check_str=neutron_policy.policy_or(
|
||||
|
@ -0,0 +1,70 @@
|
||||
# Copyright 2024 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.
|
||||
#
|
||||
|
||||
import json
|
||||
|
||||
from alembic import op
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import strutils
|
||||
import sqlalchemy as sa
|
||||
|
||||
# Add port trusted attribute
|
||||
#
|
||||
# Revision ID: 5bcb7b31ec7d
|
||||
# Revises: 175fa80908e1
|
||||
# Create Date: 2024-08-06 12:44:37.193211
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '5bcb7b31ec7d'
|
||||
down_revision = '175fa80908e1'
|
||||
|
||||
|
||||
def upgrade():
|
||||
port_trusted_table = op.create_table(
|
||||
'porttrusted',
|
||||
sa.Column('port_id',
|
||||
sa.String(36),
|
||||
sa.ForeignKey('ports.id',
|
||||
ondelete="CASCADE"),
|
||||
primary_key=True),
|
||||
sa.Column('trusted',
|
||||
sa.Boolean,
|
||||
nullable=True))
|
||||
|
||||
# A simple model of the ml2_port_bindings table, just to get and update
|
||||
# binding:profile fields where needed
|
||||
port_binding_table = sa.Table(
|
||||
'ml2_port_bindings', sa.MetaData(),
|
||||
sa.Column('port_id', sa.String(length=36), nullable=False),
|
||||
sa.Column('profile', sa.String(length=4095)))
|
||||
|
||||
session = sa.orm.Session(bind=op.get_bind())
|
||||
for row in session.query(port_binding_table).all():
|
||||
if len(row[1]) == 0:
|
||||
continue
|
||||
try:
|
||||
profile = jsonutils.loads(row[1])
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
trusted = profile.pop('trusted', None)
|
||||
if trusted is None:
|
||||
continue
|
||||
session.execute(port_trusted_table.insert().values(
|
||||
port_id=row[0],
|
||||
trusted=strutils.bool_from_string(trusted)))
|
||||
session.execute(port_binding_table.update().values(
|
||||
profile=jsonutils.dumps(profile) if profile else '').where(
|
||||
port_binding_table.c.port_id == row[0]))
|
||||
session.commit()
|
@ -1 +1 @@
|
||||
175fa80908e1
|
||||
5bcb7b31ec7d
|
||||
|
37
neutron/db/models/port_trusted.py
Normal file
37
neutron/db/models/port_trusted.py
Normal file
@ -0,0 +1,37 @@
|
||||
# Copyright 2024 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from neutron_lib.db import model_base
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import orm
|
||||
|
||||
from neutron.db import models_v2
|
||||
|
||||
|
||||
class PortTrusted(model_base.BASEV2):
|
||||
__tablename__ = 'porttrusted'
|
||||
|
||||
port_id = sa.Column(sa.String(36),
|
||||
sa.ForeignKey('ports.id', ondelete='CASCADE'),
|
||||
primary_key=True)
|
||||
trusted = sa.Column(sa.Boolean, nullable=True)
|
||||
port = orm.relationship(
|
||||
models_v2.Port,
|
||||
load_on_pending=True,
|
||||
backref=orm.backref('trusted',
|
||||
lazy='subquery',
|
||||
uselist=False,
|
||||
cascade='delete'))
|
||||
revises_on_change = ('port', )
|
66
neutron/db/port_trusted_db.py
Normal file
66
neutron/db/port_trusted_db.py
Normal file
@ -0,0 +1,66 @@
|
||||
# Copyright (c) 2024 Red Hat, 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.
|
||||
|
||||
from neutron_lib.api.definitions import portbindings
|
||||
from neutron_lib import constants as n_const
|
||||
|
||||
from neutron.extensions import port_trusted_vif
|
||||
from neutron.objects.port.extensions import port_trusted as trusted_obj
|
||||
|
||||
|
||||
class PortTrustedDbMixin(object):
|
||||
"""Mixin class to add trusted extension to a port"""
|
||||
|
||||
@staticmethod
|
||||
def _set_portbinding_profile(data, trusted):
|
||||
try:
|
||||
data[portbindings.PROFILE]['trusted'] = trusted
|
||||
except (AttributeError, KeyError):
|
||||
data[portbindings.PROFILE] = {'trusted': trusted}
|
||||
|
||||
def _process_create_port(self, context, data, result):
|
||||
trusted = data.get(port_trusted_vif.TRUSTED_VIF)
|
||||
if trusted is n_const.ATTR_NOT_SPECIFIED:
|
||||
result[port_trusted_vif.TRUSTED_VIF] = None
|
||||
return
|
||||
|
||||
obj = trusted_obj.PortTrusted(
|
||||
context, port_id=result['id'], trusted=trusted)
|
||||
obj.create()
|
||||
result[port_trusted_vif.TRUSTED_VIF] = trusted
|
||||
self._set_portbinding_profile(result, trusted)
|
||||
|
||||
def _process_update_port(self, context, data, result):
|
||||
trusted = data.get(port_trusted_vif.TRUSTED_VIF)
|
||||
if trusted is None or trusted is n_const.ATTR_NOT_SPECIFIED:
|
||||
result[port_trusted_vif.TRUSTED_VIF] = None
|
||||
return
|
||||
|
||||
obj = trusted_obj.PortTrusted.get_object(
|
||||
context, port_id=result['id'])
|
||||
if obj:
|
||||
obj.trusted = trusted
|
||||
obj.update()
|
||||
result[port_trusted_vif.TRUSTED_VIF] = trusted
|
||||
self._set_portbinding_profile(result, trusted)
|
||||
else:
|
||||
self._process_create_port(context, data, result)
|
||||
|
||||
def _extend_port_dict(self, response_data, db_data):
|
||||
if db_data.trusted is not None:
|
||||
trusted = db_data.trusted.trusted
|
||||
response_data[port_trusted_vif.TRUSTED_VIF] = trusted
|
||||
self._set_portbinding_profile(response_data, trusted)
|
||||
else:
|
||||
response_data[port_trusted_vif.TRUSTED_VIF] = None
|
77
neutron/extensions/port_trusted_vif.py
Normal file
77
neutron/extensions/port_trusted_vif.py
Normal file
@ -0,0 +1,77 @@
|
||||
# Copyright (c) 2024 Red Hat, 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.
|
||||
|
||||
from neutron_lib.api import converters
|
||||
from neutron_lib.api.definitions import port
|
||||
from neutron_lib.api.definitions import portbindings
|
||||
from neutron_lib.api import extensions as api_extensions
|
||||
from neutron_lib import constants
|
||||
|
||||
|
||||
# TODO(slaweq): use api definition from neutron-lib once
|
||||
# https://review.opendev.org/c/openstack/neutron-lib/+/923860
|
||||
# will be merged and released
|
||||
|
||||
ALIAS = 'port-trusted-vif'
|
||||
NAME = "Port trusted vif"
|
||||
DESCRIPTION = "Expose port 'trusted' attribute in the API"
|
||||
UPDATED_TIMESTAMP = "2024-07-10T10:00:00-00:00"
|
||||
RESOURCE_NAME = port.RESOURCE_NAME
|
||||
COLLECTION_NAME = port.COLLECTION_NAME
|
||||
TRUSTED_VIF = 'trusted'
|
||||
|
||||
RESOURCE_ATTRIBUTE_MAP = {
|
||||
COLLECTION_NAME: {
|
||||
TRUSTED_VIF: {
|
||||
'allow_post': True,
|
||||
'allow_put': True,
|
||||
'convert_to': converters.convert_to_boolean,
|
||||
'enforce_policy': True,
|
||||
'required_by_policy': False,
|
||||
'default': constants.ATTR_NOT_SPECIFIED,
|
||||
'is_visible': True,
|
||||
'validate': {'type:boolean': None}
|
||||
}
|
||||
},
|
||||
}
|
||||
REQUIRED_EXTENSIONS = [portbindings.ALIAS]
|
||||
OPTIONAL_EXTENSIONS = []
|
||||
|
||||
|
||||
class Port_trusted_vif(api_extensions.ExtensionDescriptor):
|
||||
|
||||
@classmethod
|
||||
def get_name(cls):
|
||||
return NAME
|
||||
|
||||
@classmethod
|
||||
def get_alias(cls):
|
||||
return ALIAS
|
||||
|
||||
@classmethod
|
||||
def get_description(cls):
|
||||
return DESCRIPTION
|
||||
|
||||
@classmethod
|
||||
def get_updated(cls):
|
||||
return UPDATED_TIMESTAMP
|
||||
|
||||
def get_required_extensions(self):
|
||||
return REQUIRED_EXTENSIONS
|
||||
|
||||
def get_extended_resources(self, version):
|
||||
if version == "2.0":
|
||||
return RESOURCE_ATTRIBUTE_MAP
|
||||
else:
|
||||
return {}
|
36
neutron/objects/port/extensions/port_trusted.py
Normal file
36
neutron/objects/port/extensions/port_trusted.py
Normal file
@ -0,0 +1,36 @@
|
||||
# Copyright (c) 2023 Red Hat, 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.
|
||||
|
||||
from neutron_lib.objects import common_types
|
||||
from oslo_versionedobjects import fields as obj_fields
|
||||
|
||||
from neutron.db.models import port_trusted
|
||||
from neutron.objects import base
|
||||
|
||||
|
||||
@base.NeutronObjectRegistry.register
|
||||
class PortTrusted(base.NeutronDbObject):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
db_model = port_trusted.PortTrusted
|
||||
|
||||
primary_keys = ['port_id']
|
||||
|
||||
fields = {
|
||||
'port_id': common_types.UUIDField(),
|
||||
'trusted': obj_fields.BooleanField(nullable=True),
|
||||
}
|
||||
|
||||
foreign_keys = {'Port': {'port_id': 'id'}}
|
@ -338,7 +338,8 @@ class Port(base.NeutronDbObject):
|
||||
# Version 1.7: Added port_device field
|
||||
# Version 1.8: Added hints field
|
||||
# Version 1.9: Added hardware_offload_type field
|
||||
VERSION = '1.9'
|
||||
# Version 1.10: Added trusted field
|
||||
VERSION = '1.10'
|
||||
|
||||
db_model = models_v2.Port
|
||||
|
||||
@ -394,6 +395,7 @@ class Port(base.NeutronDbObject):
|
||||
'numa_affinity_policy': obj_fields.StringField(nullable=True),
|
||||
'device_profile': obj_fields.StringField(nullable=True),
|
||||
'hardware_offload_type': obj_fields.StringField(nullable=True),
|
||||
'trusted': obj_fields.BooleanField(nullable=True),
|
||||
|
||||
# TODO(ihrachys): consider adding a 'dns_assignment' fully synthetic
|
||||
# field in later object iterations
|
||||
@ -420,6 +422,7 @@ class Port(base.NeutronDbObject):
|
||||
'qos_network_policy_id',
|
||||
'security',
|
||||
'security_group_ids',
|
||||
'trusted',
|
||||
]
|
||||
|
||||
fields_need_translation = {
|
||||
@ -591,6 +594,10 @@ class Port(base.NeutronDbObject):
|
||||
db_obj.hardware_offload_type.hardware_offload_type)
|
||||
fields_to_change.append('hardware_offload_type')
|
||||
|
||||
if db_obj.get('trusted') is not None:
|
||||
self.trusted = db_obj.trusted.trusted
|
||||
fields_to_change.append('trusted')
|
||||
|
||||
self.obj_reset_changes(fields_to_change)
|
||||
|
||||
def obj_make_compatible(self, primitive, target_version):
|
||||
@ -627,6 +634,8 @@ class Port(base.NeutronDbObject):
|
||||
primitive.pop('hints', None)
|
||||
if _target_version < (1, 9):
|
||||
primitive.pop('hardware_offload_type', None)
|
||||
if _target_version < (1, 10):
|
||||
primitive.pop('trusted', None)
|
||||
|
||||
@classmethod
|
||||
@db_api.CONTEXT_READER
|
||||
|
46
neutron/plugins/ml2/extensions/port_trusted.py
Normal file
46
neutron/plugins/ml2/extensions/port_trusted.py
Normal file
@ -0,0 +1,46 @@
|
||||
# Copyright 2023 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from neutron_lib.plugins.ml2 import api
|
||||
from oslo_log import log as logging
|
||||
|
||||
from neutron.db import port_trusted_db
|
||||
from neutron.extensions import port_trusted_vif
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PortTrustedExtensionDriver(
|
||||
api.ExtensionDriver,
|
||||
port_trusted_db.PortTrustedDbMixin):
|
||||
|
||||
_supported_extension_alias = port_trusted_vif.ALIAS
|
||||
|
||||
def initialize(self):
|
||||
LOG.info('PortTrustedExtensionDriver initialization complete')
|
||||
|
||||
@property
|
||||
def extension_alias(self):
|
||||
return self._supported_extension_alias
|
||||
|
||||
def process_create_port(self, context, data, result):
|
||||
self._process_create_port(context, data, result)
|
||||
|
||||
def process_update_port(self, context, data, result):
|
||||
self._process_update_port(context, data, result)
|
||||
|
||||
def extend_port_dict(self, session, port_db, result):
|
||||
self._extend_port_dict(result, port_db)
|
@ -563,7 +563,17 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
|
||||
if profile not in (None, const.ATTR_NOT_SPECIFIED,
|
||||
self._get_profile(binding)):
|
||||
binding.profile = jsonutils.dumps(profile)
|
||||
profile_json = jsonutils.dumps(profile)
|
||||
# TODO(slaweq): Remove warning and raise InvalidInput exception
|
||||
# instead in the 2026.1 release
|
||||
if 'trusted' in profile_json:
|
||||
LOG.warning("Marking VIF as 'trusted' directly through the "
|
||||
"'binding:profile' field of the port is "
|
||||
"deprecated and will be forbidden in future. "
|
||||
"Please enable 'port_trusted' ML2 plugin's "
|
||||
"extension and use 'trusted' field of the port "
|
||||
"instead")
|
||||
binding.profile = profile_json
|
||||
if len(binding.profile) > models.BINDING_PROFILE_LEN:
|
||||
msg = _("binding:profile value too large")
|
||||
raise exc.InvalidInput(error_message=msg)
|
||||
@ -897,6 +907,9 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
port[portbindings.HOST_ID] = binding.host
|
||||
port[portbindings.VIF_TYPE] = binding.vif_type
|
||||
port[portbindings.VIF_DETAILS] = self._get_vif_details(binding)
|
||||
port_trusted = port.get('trusted')
|
||||
if port_trusted is not None:
|
||||
port[portbindings.PROFILE]['trusted'] = port_trusted
|
||||
|
||||
def _update_port_dict_bound_drivers(self, port, binding_levels):
|
||||
levels = {str(bl.level): bl.driver for bl in binding_levels}
|
||||
@ -2680,7 +2693,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
'and shared filesystem ports') % port['id']
|
||||
raise exc.BadRequest(resource='port', msg=msg)
|
||||
|
||||
def _make_port_binding_dict(self, binding, fields=None):
|
||||
def _make_port_binding_dict(self, binding, fields=None, port=None):
|
||||
res = {key: binding[key] for key in (
|
||||
pbe_ext.HOST, pbe_ext.VIF_TYPE, pbe_ext.VNIC_TYPE, pbe_ext.STATUS)}
|
||||
if isinstance(binding, ports_obj.PortBinding):
|
||||
@ -2689,8 +2702,19 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
else:
|
||||
res[pbe_ext.PROFILE] = self._get_profile(binding)
|
||||
res[pbe_ext.VIF_DETAILS] = self._get_vif_details(binding)
|
||||
# If port object was passed, get e.g. trusted field from it and add it
|
||||
# to the binding:profile
|
||||
if port:
|
||||
self._extend_port_binding_dict_with_synthetic_fields(res, port)
|
||||
return db_utils.resource_fields(res, fields)
|
||||
|
||||
def _extend_port_binding_dict_with_synthetic_fields(self, binding, port):
|
||||
if binding[pbe_ext.VIF_TYPE] == portbindings.VIF_TYPE_UNBOUND:
|
||||
# For unbound port there is no need to extend binding dict
|
||||
return
|
||||
if port.trusted is not None:
|
||||
binding[pbe_ext.PROFILE]['trusted'] = port.trusted
|
||||
|
||||
def _get_port_binding_attrs(self, binding, host=None):
|
||||
return {portbindings.VNIC_TYPE: binding.get(pbe_ext.VNIC_TYPE),
|
||||
portbindings.HOST_ID: binding.get(pbe_ext.HOST) or host,
|
||||
@ -2716,7 +2740,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
def create_port_binding(self, context, port_id, binding):
|
||||
attrs = binding[pbe_ext.RESOURCE_NAME]
|
||||
with db_api.CONTEXT_WRITER.using(context):
|
||||
port_db = self._get_port(context, port_id)
|
||||
port = ports_obj.Port.get_object(context, id=port_id)
|
||||
port_db = port.db_obj
|
||||
self._validate_port_supports_multiple_bindings(port_db)
|
||||
if self._get_binding_for_host(port_db.port_bindings,
|
||||
attrs[pbe_ext.HOST]):
|
||||
@ -2755,7 +2780,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
with db_api.CONTEXT_WRITER.using(context):
|
||||
bind_context._binding.persist_state_to_session(context.session)
|
||||
db.set_binding_levels(context, bind_context._binding_levels)
|
||||
return self._make_port_binding_dict(bind_context._binding)
|
||||
return self._make_port_binding_dict(bind_context._binding, port=port)
|
||||
|
||||
@utils.transaction_guard
|
||||
@db_api.retry_if_session_inactive()
|
||||
@ -2771,7 +2796,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
bindings = ports_obj.PortBinding.get_objects(
|
||||
context, _pager=pager, port_id=port_id, **filters)
|
||||
|
||||
return [self._make_port_binding_dict(binding, fields)
|
||||
return [self._make_port_binding_dict(binding, fields, port)
|
||||
for binding in bindings]
|
||||
|
||||
@utils.transaction_guard
|
||||
@ -2785,7 +2810,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
port_id=port_id)
|
||||
if not binding:
|
||||
raise exc.PortBindingNotFound(port_id=port_id, host=host)
|
||||
return self._make_port_binding_dict(binding, fields)
|
||||
return self._make_port_binding_dict(binding, fields, port)
|
||||
|
||||
def _get_binding_for_host(self, bindings, host):
|
||||
for binding in bindings:
|
||||
@ -2797,7 +2822,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
def update_port_binding(self, context, host, port_id, binding):
|
||||
attrs = binding[pbe_ext.RESOURCE_NAME]
|
||||
with db_api.CONTEXT_WRITER.using(context):
|
||||
port_db = self._get_port(context, port_id)
|
||||
port = ports_obj.Port.get_object(context, id=port_id)
|
||||
port_db = port.db_obj
|
||||
self._validate_port_supports_multiple_bindings(port_db)
|
||||
original_binding = self._get_binding_for_host(
|
||||
port_db.port_bindings, host)
|
||||
@ -2824,7 +2850,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
with db_api.CONTEXT_WRITER.using(context):
|
||||
bind_context._binding.persist_state_to_session(context.session)
|
||||
db.set_binding_levels(context, bind_context._binding_levels)
|
||||
return self._make_port_binding_dict(bind_context._binding)
|
||||
return self._make_port_binding_dict(bind_context._binding, port=port)
|
||||
|
||||
@utils.transaction_guard
|
||||
@db_api.retry_if_session_inactive()
|
||||
@ -2834,7 +2860,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
# fixed
|
||||
if isinstance(port_id, dict):
|
||||
port_id = port_id['port_id']
|
||||
port_db = self._get_port(context, port_id)
|
||||
port = ports_obj.Port.get_object(context, id=port_id)
|
||||
port_db = port.db_obj
|
||||
self._validate_port_supports_multiple_bindings(port_db)
|
||||
active_binding = p_utils.get_port_binding_by_status_and_host(
|
||||
port_db.port_bindings, const.ACTIVE)
|
||||
@ -2874,7 +2901,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
network['id'])
|
||||
self.notifier.binding_activate(context, port_id,
|
||||
inactive_binding.host)
|
||||
return self._make_port_binding_dict(cur_context._binding)
|
||||
return self._make_port_binding_dict(cur_context._binding,
|
||||
port=port)
|
||||
raise exc.PortBindingError(port_id=port_id, host=host)
|
||||
|
||||
@utils.transaction_guard
|
||||
|
@ -65,6 +65,7 @@ NETWORK_API_EXTENSIONS+=",port-mac-address-regenerate"
|
||||
NETWORK_API_EXTENSIONS+=",port-numa-affinity-policy"
|
||||
NETWORK_API_EXTENSIONS+=",port-numa-affinity-policy-socket"
|
||||
NETWORK_API_EXTENSIONS+=",port-security-groups-filtering"
|
||||
NETWORK_API_EXTENSIONS+=",port-trusted-vif"
|
||||
NETWORK_API_EXTENSIONS+=",segment"
|
||||
NETWORK_API_EXTENSIONS+=",segments-peer-subnet-host-routes"
|
||||
NETWORK_API_EXTENSIONS+=",service-type"
|
||||
|
@ -552,6 +552,16 @@ class AdminTests(PortAPITestCase):
|
||||
'create_port:hints',
|
||||
self.alt_target))
|
||||
|
||||
def test_create_port_with_trusted_field(self):
|
||||
self.assertTrue(
|
||||
policy.enforce(self.context,
|
||||
'create_port:trusted',
|
||||
self.target))
|
||||
self.assertTrue(
|
||||
policy.enforce(self.context,
|
||||
'create_port:trusted',
|
||||
self.alt_target))
|
||||
|
||||
def test_get_port(self):
|
||||
self.assertTrue(
|
||||
policy.enforce(self.context, 'get_port', self.target))
|
||||
@ -606,6 +616,14 @@ class AdminTests(PortAPITestCase):
|
||||
policy.enforce(
|
||||
self.context, 'get_port:hints', self.alt_target))
|
||||
|
||||
def test_get_port_trusted_field(self):
|
||||
self.assertTrue(
|
||||
policy.enforce(
|
||||
self.context, 'get_port:trusted', self.target))
|
||||
self.assertTrue(
|
||||
policy.enforce(
|
||||
self.context, 'get_port:trusted', self.alt_target))
|
||||
|
||||
def test_get_ports_tags(self):
|
||||
self.assertTrue(
|
||||
policy.enforce(self.context, 'get_ports_tags', self.target))
|
||||
@ -747,6 +765,16 @@ class AdminTests(PortAPITestCase):
|
||||
'update_port:hints',
|
||||
self.alt_target))
|
||||
|
||||
def test_update_port_trusted_field(self):
|
||||
self.assertTrue(
|
||||
policy.enforce(self.context,
|
||||
'update_port:trusted',
|
||||
self.target))
|
||||
self.assertTrue(
|
||||
policy.enforce(self.context,
|
||||
'update_port:trusted',
|
||||
self.alt_target))
|
||||
|
||||
def test_delete_port(self):
|
||||
self.assertTrue(
|
||||
policy.enforce(self.context, 'delete_port', self.target))
|
||||
@ -899,6 +927,18 @@ class ProjectManagerTests(AdminTests):
|
||||
self.context, 'create_port:hints',
|
||||
self.alt_target)
|
||||
|
||||
def test_create_port_with_trusted_field(self):
|
||||
self.assertRaises(
|
||||
base_policy.PolicyNotAuthorized,
|
||||
policy.enforce,
|
||||
self.context, 'create_port:trusted',
|
||||
self.target)
|
||||
self.assertRaises(
|
||||
base_policy.PolicyNotAuthorized,
|
||||
policy.enforce,
|
||||
self.context, 'create_port:trusted',
|
||||
self.alt_target)
|
||||
|
||||
def test_get_port(self):
|
||||
self.assertTrue(
|
||||
policy.enforce(self.context, 'get_port', self.target))
|
||||
@ -966,6 +1006,16 @@ class ProjectManagerTests(AdminTests):
|
||||
policy.enforce, self.context, 'get_port:hints',
|
||||
self.alt_target)
|
||||
|
||||
def test_get_port_trusted_field(self):
|
||||
self.assertRaises(
|
||||
base_policy.PolicyNotAuthorized,
|
||||
policy.enforce, self.context, 'get_port:trusted',
|
||||
self.target)
|
||||
self.assertRaises(
|
||||
base_policy.PolicyNotAuthorized,
|
||||
policy.enforce, self.context, 'get_port:trusted',
|
||||
self.alt_target)
|
||||
|
||||
def test_get_ports_tags(self):
|
||||
self.assertTrue(
|
||||
policy.enforce(self.context, 'get_ports_tags', self.target))
|
||||
@ -1120,6 +1170,16 @@ class ProjectManagerTests(AdminTests):
|
||||
policy.enforce,
|
||||
self.context, 'update_port:hints', self.alt_target)
|
||||
|
||||
def test_update_port_trusted_field(self):
|
||||
self.assertRaises(
|
||||
base_policy.PolicyNotAuthorized,
|
||||
policy.enforce,
|
||||
self.context, 'update_port:trusted', self.target)
|
||||
self.assertRaises(
|
||||
base_policy.PolicyNotAuthorized,
|
||||
policy.enforce,
|
||||
self.context, 'update_port:trusted', self.alt_target)
|
||||
|
||||
def test_update_ports_tags(self):
|
||||
self.assertTrue(
|
||||
policy.enforce(self.context, 'update_ports_tags', self.target))
|
||||
|
@ -538,8 +538,8 @@ class NeutronDbPluginV2TestCase(testlib_api.WebTestCase):
|
||||
'mac_address', 'name', 'fixed_ips',
|
||||
'tenant_id', 'device_owner', 'security_groups',
|
||||
'propagate_uplink_status', 'numa_affinity_policy',
|
||||
'device_profile', 'hints', 'hardware_offload_type') +
|
||||
(arg_list or ())):
|
||||
'device_profile', 'hints', 'hardware_offload_type',
|
||||
'trusted') + (arg_list or ())):
|
||||
# Arg must be present
|
||||
if arg in kwargs:
|
||||
data['port'][arg] = kwargs[arg]
|
||||
|
70
neutron/tests/unit/extensions/test_port_trusted_vif.py
Normal file
70
neutron/tests/unit/extensions/test_port_trusted_vif.py
Normal file
@ -0,0 +1,70 @@
|
||||
# Copyright (c) 2023 Red Hat, 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.
|
||||
|
||||
import ddt
|
||||
from neutron_lib.api.definitions import portbindings
|
||||
from neutron_lib import context
|
||||
from oslo_config import cfg
|
||||
|
||||
from neutron.db import port_trusted_db
|
||||
from neutron.extensions import port_trusted_vif as apidef
|
||||
from neutron.plugins.ml2 import plugin
|
||||
from neutron.tests.unit.db import test_db_base_plugin_v2
|
||||
|
||||
|
||||
class PortTrustedExtensionTestPlugin(
|
||||
plugin.Ml2Plugin,
|
||||
port_trusted_db.PortTrustedDbMixin):
|
||||
"""Test plugin to mixin the port trusted extension."""
|
||||
|
||||
supported_extension_aliases = [apidef.ALIAS, portbindings.ALIAS]
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class PortTrustedExtensionTestCase(
|
||||
test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
|
||||
"""Test API extension port-trusted-vif attributes."""
|
||||
|
||||
def setUp(self, *args):
|
||||
plugin = ('neutron.tests.unit.extensions.test_port_trusted_vif.'
|
||||
'PortTrustedExtensionTestPlugin')
|
||||
extension_drivers = ['port_trusted']
|
||||
cfg.CONF.set_override('extension_drivers', extension_drivers, 'ml2')
|
||||
super().setUp(plugin=plugin)
|
||||
self.ctx = context.get_admin_context()
|
||||
|
||||
def _create_and_check_port_with_trusted_field(self, trusted):
|
||||
name = 'port-trusted-vif'
|
||||
keys = [('name', name),
|
||||
('admin_state_up', True),
|
||||
('trusted', trusted)]
|
||||
port_args = {'name': name}
|
||||
if trusted is not None:
|
||||
port_args['trusted'] = trusted
|
||||
with self.port(is_admin=True, **port_args) as port:
|
||||
for k, v in keys:
|
||||
self.assertEqual(v, port['port'][k])
|
||||
if trusted is not None:
|
||||
self.assertEqual(trusted,
|
||||
port['port']['binding:profile']['trusted'])
|
||||
else:
|
||||
self.assertNotIn('trusted',
|
||||
port['port']['binding:profile'].keys())
|
||||
|
||||
def test_create_port_with_trusted_field(self):
|
||||
self._create_and_check_port_with_trusted_field(True)
|
||||
self._create_and_check_port_with_trusted_field(False)
|
||||
|
||||
def test_create_port_with_trusted_field_not_set(self):
|
||||
self._create_and_check_port_with_trusted_field(None)
|
@ -0,0 +1,36 @@
|
||||
# Copyright (c) 2024 Red Hat, 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.
|
||||
|
||||
from neutron.objects.port.extensions import port_trusted
|
||||
from neutron.tests.unit.objects import test_base as obj_test_base
|
||||
from neutron.tests.unit import testlib_api
|
||||
|
||||
|
||||
class PortTrustedIfaceObjectTestCase(
|
||||
obj_test_base.BaseObjectIfaceTestCase):
|
||||
|
||||
_test_class = port_trusted.PortTrusted
|
||||
|
||||
|
||||
class PortTrustedDbObjectTestCase(
|
||||
obj_test_base.BaseDbObjectTestCase,
|
||||
testlib_api.SqlTestCase):
|
||||
|
||||
_test_class = port_trusted.PortTrusted
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.update_obj_fields(
|
||||
{'port_id': lambda: self._create_test_port_id(),
|
||||
'trusted': True})
|
@ -73,7 +73,7 @@ object_data = {
|
||||
'NetworkSegment': '1.0-57b7f2960971e3b95ded20cbc59244a8',
|
||||
'NetworkSegmentRange': '1.0-bdec1fffc9058ea676089b1f2f2b3cf3',
|
||||
'NetworkSubnetLock': '1.0-140de39d4b86ae346dc3d70b885bea53',
|
||||
'Port': '1.9-25f8da7ed95f1538f9e08657b0b450c1',
|
||||
'Port': '1.10-ae84f686bfc3deb4017495134da6ef04',
|
||||
'PortHardwareOffloadType': '1.0-5f424d02b144fd1832ac3e6b03662674',
|
||||
'PortDeviceProfile': '1.0-b98c7083cc3e93d176fd7a91ae13af32',
|
||||
'PortHints': '1.0-9ebf6e12fa427809476a92c7432352b8',
|
||||
@ -85,6 +85,7 @@ object_data = {
|
||||
'PortForwarding': '1.3-402b1fb5a754808b82a966c95f468113',
|
||||
'PortSecurity': '1.0-b30802391a87945ee9c07582b4ff95e3',
|
||||
'PortUplinkStatusPropagation': '1.1-f0a4ca451a941910376c33616dea5de2',
|
||||
'PortTrusted': '1.0-8312fb91937412cdeb92c3279059c7ce',
|
||||
'ProviderResourceAssociation': '1.0-05ab2d5a3017e5ce9dd381328f285f34',
|
||||
'ProvisioningBlock': '1.0-c19d6d05bfa8143533471c1296066125',
|
||||
'QosBandwidthLimitRule': '1.5-51b662b12a8d1dfa89288d826c6d26d3',
|
||||
|
@ -535,6 +535,12 @@ class PortDbObjectTestCase(obj_test_base.BaseDbObjectTestCase,
|
||||
self.assertNotIn('hints',
|
||||
port_v1_7['versioned_object.data'])
|
||||
|
||||
def test_v1_10_to_v1_9_drops_trusted(self):
|
||||
port_new = self._create_test_port(trusted=True)
|
||||
port_v1_9 = port_new.obj_to_primitive(target_version='1.9')
|
||||
self.assertNotIn('trusted',
|
||||
port_v1_9['versioned_object.data'])
|
||||
|
||||
def test_get_ports_ids_by_security_groups_except_router(self):
|
||||
sg_id = self._create_test_security_group_id()
|
||||
filter_owner = constants.ROUTER_INTERFACE_OWNERS_SNAT
|
||||
|
@ -354,6 +354,7 @@ class ExtendedPortBindingTestCase(test_plugin.NeutronDbPluginV2TestCase):
|
||||
cfg.CONF.set_override('mechanism_drivers',
|
||||
['logger', 'test'],
|
||||
'ml2')
|
||||
cfg.CONF.set_override('extension_drivers', ['port_trusted'], 'ml2')
|
||||
|
||||
driver_type.register_ml2_drivers_vlan_opts()
|
||||
cfg.CONF.set_override('network_vlan_ranges',
|
||||
@ -431,9 +432,15 @@ class ExtendedPortBindingTestCase(test_plugin.NeutronDbPluginV2TestCase):
|
||||
subresource='bindings', sub_id=host).get_response(self.api)
|
||||
return response
|
||||
|
||||
def _create_port_and_binding(self, **kwargs):
|
||||
device_owner = '%s%s' % (const.DEVICE_OWNER_COMPUTE_PREFIX, 'nova')
|
||||
with self.port(device_owner=device_owner) as port:
|
||||
# todo(slaweq): here I can add trusted to be checked too
|
||||
def _create_port_and_binding(self, trusted=None, **kwargs):
|
||||
port_kwargs = {
|
||||
'device_owner': '%s%s' % (
|
||||
const.DEVICE_OWNER_COMPUTE_PREFIX, 'nova')}
|
||||
if trusted is not None:
|
||||
port_kwargs['trusted'] = trusted
|
||||
port_kwargs['is_admin'] = True
|
||||
with self.port(**port_kwargs) as port:
|
||||
port_id = port['port']['id']
|
||||
binding = self._make_port_binding(self.fmt, port_id, self.host,
|
||||
**kwargs)['binding']
|
||||
@ -447,12 +454,13 @@ class ExtendedPortBindingTestCase(test_plugin.NeutronDbPluginV2TestCase):
|
||||
self.assertEqual({'port_filter': False},
|
||||
binding[pbe_ext.VIF_DETAILS])
|
||||
|
||||
def _assert_unbound_port_binding(self, binding):
|
||||
def _assert_unbound_port_binding(self, binding, expected_profile=None):
|
||||
self.assertFalse(binding[pbe_ext.HOST])
|
||||
self.assertEqual(portbindings.VIF_TYPE_UNBOUND,
|
||||
binding[pbe_ext.VIF_TYPE])
|
||||
self.assertEqual({}, binding[pbe_ext.VIF_DETAILS])
|
||||
self.assertEqual({}, binding[pbe_ext.PROFILE])
|
||||
expected_profile = expected_profile or {}
|
||||
self.assertEqual(expected_profile, binding[pbe_ext.PROFILE])
|
||||
|
||||
def test_create_port_binding(self):
|
||||
profile = {'key1': 'value1'}
|
||||
@ -641,12 +649,32 @@ class ExtendedPortBindingTestCase(test_plugin.NeutronDbPluginV2TestCase):
|
||||
self.assertEqual(1, len(retrieved_bindings))
|
||||
self._assert_bound_port_binding(retrieved_bindings[0])
|
||||
|
||||
def test_list_port_bindings_with_trusted_field_set(self):
|
||||
port, new_binding = self._create_port_and_binding(trusted=True)
|
||||
retrieved_bindings = self._list_port_bindings(
|
||||
port['id'], raw_response=False)['bindings']
|
||||
self.assertEqual(2, len(retrieved_bindings))
|
||||
self._assert_unbound_port_binding(
|
||||
utils.get_port_binding_by_status_and_host(
|
||||
retrieved_bindings, const.ACTIVE))
|
||||
bound_port_binding = utils.get_port_binding_by_status_and_host(
|
||||
retrieved_bindings, const.INACTIVE, host=self.host)
|
||||
self._assert_bound_port_binding(bound_port_binding)
|
||||
self.assertTrue(bound_port_binding['profile']['trusted'])
|
||||
|
||||
def test_show_port_binding(self):
|
||||
port, new_binding = self._create_port_and_binding()
|
||||
retrieved_binding = self._show_port_binding(
|
||||
port['id'], self.host, raw_response=False)['binding']
|
||||
self._assert_bound_port_binding(retrieved_binding)
|
||||
|
||||
def test_show_port_binding_with_trusted_field_set(self):
|
||||
port, new_binding = self._create_port_and_binding(trusted=True)
|
||||
retrieved_binding = self._show_port_binding(
|
||||
port['id'], self.host, raw_response=False)['binding']
|
||||
self._assert_bound_port_binding(retrieved_binding)
|
||||
self.assertTrue(retrieved_binding['profile']['trusted'])
|
||||
|
||||
def test_show_port_binding_with_fields(self):
|
||||
port, new_binding = self._create_port_and_binding()
|
||||
fields = 'fields=%s' % pbe_ext.HOST
|
||||
|
@ -0,0 +1,17 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
New ML2 plugin extension ``port_trusted`` is now available. This extension
|
||||
implements the ``port_trusted_vif`` API extension which adds to the port resource
|
||||
a new boolean field called ``trusted``. This field should be used by admin users
|
||||
to set the port as trusted what was previously possible only through the port's
|
||||
``binding:profile`` dictionary. Value of the ``trusted`` field is still visible
|
||||
in the port's ``binding:profile`` dictionary so that for example Nova still has
|
||||
it where it is expected to be.
|
||||
|
||||
deprecations:
|
||||
- |
|
||||
Setting ``trusted`` key directly in the port's ``binding:profile`` is
|
||||
deprecated and will be forbidden in future releases. Dedicated port's
|
||||
attribute ``trusted``, added by the API extension ``port_trusted_vif``
|
||||
should be used instead.
|
@ -125,6 +125,7 @@ neutron.ml2.extension_drivers =
|
||||
port_device_profile = neutron.plugins.ml2.extensions.port_device_profile:PortDeviceProfileExtensionDriver
|
||||
port_hardware_offload_type = neutron.plugins.ml2.extensions.port_hardware_offload_type:PortHardwareOffloadTypeExtensionDriver
|
||||
port_numa_affinity_policy = neutron.plugins.ml2.extensions.port_numa_affinity_policy:PortNumaAffinityPolicyExtensionDriver
|
||||
port_trusted = neutron.plugins.ml2.extensions.port_trusted:PortTrustedExtensionDriver
|
||||
uplink_status_propagation = neutron.plugins.ml2.extensions.uplink_status_propagation:UplinkStatusPropagationExtensionDriver
|
||||
tag_ports_during_bulk_creation = neutron.plugins.ml2.extensions.tag_ports_during_bulk_creation:TagPortsDuringBulkCreationExtensionDriver
|
||||
subnet_dns_publish_fixed_ip = neutron.plugins.ml2.extensions.subnet_dns_publish_fixed_ip:SubnetDNSPublishFixedIPExtensionDriver
|
||||
|
Loading…
Reference in New Issue
Block a user