Add propagate_uplink_status to port
Introduce an attribute 'propagate_uplink_status' to port. This attribute can be implemented for VF port to indicate if the VF link state should follow the state of the PF. Note: ML2 extension driver loaded on request via configuration: [ml2] extension_drivers = uplink_status_propagation Other related patches: * neutron-lib: https://review.openstack.org/#/c/571821/ * tempest test: https://review.openstack.org/#/c/586719/ * OSC: https://review.openstack.org/#/c/586684/ * neutronclient: https://review.openstack.org/#/c/586712/ APIImpact Add 'propagate_uplink_status' attribute to 'port' resource Change-Id: Ie8260c332e24c1880f9f82e6b6dacca8415be842 Close-Bug: #1722720
This commit is contained in:
parent
4b7a070b3f
commit
f0678b9b09
3
devstack/lib/uplink_status_propagation
Normal file
3
devstack/lib/uplink_status_propagation
Normal file
@ -0,0 +1,3 @@
|
||||
function configure_uplink_status_propagation_extension {
|
||||
neutron_ml2_extension_driver_add "uplink_status_propagation"
|
||||
}
|
@ -12,6 +12,7 @@ source $LIBDIR/segments
|
||||
source $LIBDIR/trunk
|
||||
source $LIBDIR/log
|
||||
source $LIBDIR/fip_port_forwarding
|
||||
source $LIBDIR/uplink_status_propagation
|
||||
|
||||
Q_BUILD_OVS_FROM_GIT=$(trueorfalse False Q_BUILD_OVS_FROM_GIT)
|
||||
|
||||
@ -30,6 +31,9 @@ if [[ "$1" == "stack" ]]; then
|
||||
fi
|
||||
;;
|
||||
post-config)
|
||||
if is_service_enabled neutron-uplink-status-propagation; then
|
||||
configure_uplink_status_propagation_extension
|
||||
fi
|
||||
if is_service_enabled q-flavors neutron-flavors; then
|
||||
configure_flavors
|
||||
fi
|
||||
|
@ -1 +1 @@
|
||||
d72db3e25539
|
||||
cada2437bf41
|
||||
|
@ -0,0 +1,41 @@
|
||||
# Copyright 2018 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.
|
||||
#
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
"""add propagate_uplink_status to port
|
||||
|
||||
Revision ID: cada2437bf41
|
||||
Revises: d72db3e25539
|
||||
Create Date: 2018-11-29 19:25:12.197590
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'cada2437bf41'
|
||||
down_revision = 'd72db3e25539'
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table('portuplinkstatuspropagation',
|
||||
sa.Column('port_id', sa.String(36),
|
||||
sa.ForeignKey('ports.id',
|
||||
ondelete="CASCADE"),
|
||||
primary_key=True, index=True),
|
||||
sa.Column('propagate_uplink_status', sa.Boolean(),
|
||||
nullable=False,
|
||||
server_default=sa.sql.false()))
|
33
neutron/db/models/uplink_status_propagation.py
Normal file
33
neutron/db/models/uplink_status_propagation.py
Normal file
@ -0,0 +1,33 @@
|
||||
# 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 PortUplinkStatusPropagation(model_base.BASEV2):
|
||||
__tablename__ = 'portuplinkstatuspropagation'
|
||||
|
||||
port_id = sa.Column(sa.String(36),
|
||||
sa.ForeignKey('ports.id', ondelete="CASCADE"),
|
||||
primary_key=True, index=True)
|
||||
propagate_uplink_status = sa.Column(sa.Boolean(), nullable=False,
|
||||
server_default=sa.sql.false())
|
||||
port = orm.relationship(
|
||||
models_v2.Port, load_on_pending=True,
|
||||
backref=orm.backref("propagate_uplink_status",
|
||||
lazy='joined', uselist=False,
|
||||
cascade='delete'))
|
||||
revises_on_change = ('port', )
|
32
neutron/db/uplink_status_propagation_db.py
Normal file
32
neutron/db/uplink_status_propagation_db.py
Normal file
@ -0,0 +1,32 @@
|
||||
# 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 uplink_status_propagation as usp
|
||||
|
||||
from neutron.objects.port.extensions import uplink_status_propagation as \
|
||||
usp_obj
|
||||
|
||||
|
||||
class UplinkStatusPropagationMixin(object):
|
||||
"""Mixin class to add uplink propagation to a port"""
|
||||
|
||||
def _process_create_port(self, context, data, res):
|
||||
obj = usp_obj.PortUplinkStatusPropagation(context, port_id=res['id'],
|
||||
propagate_uplink_status=data[usp.PROPAGATE_UPLINK_STATUS])
|
||||
obj.create()
|
||||
res[usp.PROPAGATE_UPLINK_STATUS] = data[usp.PROPAGATE_UPLINK_STATUS]
|
||||
|
||||
@staticmethod
|
||||
def _extend_port_dict(port_res, port_db):
|
||||
usp_db = port_db.get(usp.PROPAGATE_UPLINK_STATUS)
|
||||
port_res[usp.PROPAGATE_UPLINK_STATUS] = (
|
||||
usp_db.propagate_uplink_status if usp_db else False)
|
18
neutron/extensions/uplink_status_propagation.py
Normal file
18
neutron/extensions/uplink_status_propagation.py
Normal file
@ -0,0 +1,18 @@
|
||||
# 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 uplink_status_propagation as apidef
|
||||
from neutron_lib.api import extensions
|
||||
|
||||
|
||||
class Uplink_status_propagation(extensions.APIExtensionDescriptor):
|
||||
api_definition = apidef
|
34
neutron/objects/port/extensions/uplink_status_propagation.py
Normal file
34
neutron/objects/port/extensions/uplink_status_propagation.py
Normal file
@ -0,0 +1,34 @@
|
||||
# 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 oslo_versionedobjects import fields as obj_fields
|
||||
|
||||
from neutron.db.models import uplink_status_propagation as db_models
|
||||
from neutron.objects import base
|
||||
from neutron.objects import common_types
|
||||
|
||||
|
||||
@base.NeutronObjectRegistry.register
|
||||
class PortUplinkStatusPropagation(base.NeutronDbObject):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = "1.0"
|
||||
|
||||
db_model = db_models.PortUplinkStatusPropagation
|
||||
|
||||
primary_keys = ['port_id']
|
||||
|
||||
fields = {
|
||||
'port_id': common_types.UUIDField(),
|
||||
'propagate_uplink_status': obj_fields.BooleanField(default=False),
|
||||
}
|
||||
|
||||
foreign_keys = {'Port': {'port_id': 'id'}}
|
@ -169,14 +169,15 @@ class EmbSwitch(object):
|
||||
vf_index = self._get_vf_index(pci_slot)
|
||||
return self.pci_dev_wrapper.get_vf_state(vf_index)
|
||||
|
||||
def set_device_state(self, pci_slot, state):
|
||||
def set_device_state(self, pci_slot, state, propagate_uplink_state):
|
||||
"""Set device state.
|
||||
|
||||
@param pci_slot: Virtual Function address
|
||||
@param state: link state
|
||||
"""
|
||||
vf_index = self._get_vf_index(pci_slot)
|
||||
return self.pci_dev_wrapper.set_vf_state(vf_index, state)
|
||||
return self.pci_dev_wrapper.set_vf_state(vf_index, state,
|
||||
auto=propagate_uplink_state)
|
||||
|
||||
def set_device_rate(self, pci_slot, rate_type, rate_kbps):
|
||||
"""Set device rate: rate (max_tx_rate), min_tx_rate
|
||||
@ -293,15 +294,15 @@ class ESwitchManager(object):
|
||||
def get_device_state(self, device_mac, pci_slot):
|
||||
"""Get device state.
|
||||
|
||||
Get the device state (up/True or down/False)
|
||||
Get the device state (up/enable, down/disable, or auto)
|
||||
@param device_mac: device mac
|
||||
@param pci_slot: VF PCI slot
|
||||
@return: device state (True/False) None if failed
|
||||
@return: device state (enable/disable/auto) None if failed
|
||||
"""
|
||||
embedded_switch = self._get_emb_eswitch(device_mac, pci_slot)
|
||||
if embedded_switch:
|
||||
return embedded_switch.get_device_state(pci_slot)
|
||||
return False
|
||||
return pci_lib.LinkState.DISABLE
|
||||
|
||||
def set_device_max_rate(self, device_mac, pci_slot, max_kbps):
|
||||
"""Set device max rate
|
||||
@ -333,18 +334,21 @@ class ESwitchManager(object):
|
||||
ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_MIN_TX_RATE,
|
||||
min_kbps)
|
||||
|
||||
def set_device_state(self, device_mac, pci_slot, admin_state_up):
|
||||
def set_device_state(self, device_mac, pci_slot, admin_state_up,
|
||||
propagate_uplink_state):
|
||||
"""Set device state
|
||||
|
||||
Sets the device state (up or down)
|
||||
@param device_mac: device mac
|
||||
@param pci_slot: pci slot
|
||||
@param admin_state_up: device admin state True/False
|
||||
@param propagate_uplink_state: follow uplink state True/False
|
||||
"""
|
||||
embedded_switch = self._get_emb_eswitch(device_mac, pci_slot)
|
||||
if embedded_switch:
|
||||
embedded_switch.set_device_state(pci_slot,
|
||||
admin_state_up)
|
||||
admin_state_up,
|
||||
propagate_uplink_state)
|
||||
|
||||
def set_device_spoofcheck(self, device_mac, pci_slot, enabled):
|
||||
"""Set device spoofcheck
|
||||
|
@ -24,6 +24,12 @@ from neutron.plugins.ml2.drivers.mech_sriov.agent.common \
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LinkState(object):
|
||||
ENABLE = "enable"
|
||||
DISABLE = "disable"
|
||||
AUTO = "auto"
|
||||
|
||||
|
||||
class PciDeviceIPWrapper(ip_lib.IPWrapper):
|
||||
"""Wrapper class for ip link commands.
|
||||
|
||||
@ -41,10 +47,6 @@ class PciDeviceIPWrapper(ip_lib.IPWrapper):
|
||||
|
||||
IP_LINK_OP_NOT_SUPPORTED = 'RTNETLINK answers: Operation not supported'
|
||||
|
||||
class LinkState(object):
|
||||
ENABLE = "enable"
|
||||
DISABLE = "disable"
|
||||
|
||||
def __init__(self, dev_name):
|
||||
super(PciDeviceIPWrapper, self).__init__()
|
||||
self.dev_name = dev_name
|
||||
@ -96,10 +98,9 @@ class PciDeviceIPWrapper(ip_lib.IPWrapper):
|
||||
return vf_to_mac_mapping
|
||||
|
||||
def get_vf_state(self, vf_index):
|
||||
"""Get vf state {True/False}
|
||||
"""Get vf state {enable/disable/auto}
|
||||
|
||||
@param vf_index: vf index
|
||||
@todo: Handle "auto" state
|
||||
"""
|
||||
try:
|
||||
out = self._as_root([], "link", ("show", self.dev_name))
|
||||
@ -112,19 +113,22 @@ class PciDeviceIPWrapper(ip_lib.IPWrapper):
|
||||
vf_details = self._parse_vf_link_show(vf_lines[0])
|
||||
if vf_details:
|
||||
state = vf_details.get("link-state",
|
||||
self.LinkState.DISABLE)
|
||||
if state != self.LinkState.DISABLE:
|
||||
return True
|
||||
return False
|
||||
LinkState.DISABLE)
|
||||
if state in (LinkState.AUTO, LinkState.ENABLE):
|
||||
return state
|
||||
return LinkState.DISABLE
|
||||
|
||||
def set_vf_state(self, vf_index, state):
|
||||
def set_vf_state(self, vf_index, state, auto=False):
|
||||
"""sets vf state.
|
||||
|
||||
@param vf_index: vf index
|
||||
@param state: required state {True/False}
|
||||
"""
|
||||
status_str = self.LinkState.ENABLE if state else \
|
||||
self.LinkState.DISABLE
|
||||
if auto:
|
||||
status_str = LinkState.AUTO
|
||||
else:
|
||||
status_str = LinkState.ENABLE if state else \
|
||||
LinkState.DISABLE
|
||||
self._set_feature(vf_index, "state", status_str)
|
||||
|
||||
def set_vf_spoofcheck(self, vf_index, enabled):
|
||||
|
@ -240,7 +240,8 @@ class SriovNicSwitchAgent(object):
|
||||
# If one of the above operations fails => resync with plugin
|
||||
return (resync_a | resync_b)
|
||||
|
||||
def treat_device(self, device, pci_slot, admin_state_up, spoofcheck=True):
|
||||
def treat_device(self, device, pci_slot, admin_state_up, spoofcheck=True,
|
||||
propagate_uplink_state=False):
|
||||
if self.eswitch_mgr.device_exists(device, pci_slot):
|
||||
try:
|
||||
self.eswitch_mgr.set_device_spoofcheck(device, pci_slot,
|
||||
@ -253,7 +254,8 @@ class SriovNicSwitchAgent(object):
|
||||
|
||||
try:
|
||||
self.eswitch_mgr.set_device_state(device, pci_slot,
|
||||
admin_state_up)
|
||||
admin_state_up,
|
||||
propagate_uplink_state)
|
||||
except exc.IpCommandOperationNotSupportedError:
|
||||
LOG.warning("Device %s does not support state change",
|
||||
device)
|
||||
@ -305,10 +307,12 @@ class SriovNicSwitchAgent(object):
|
||||
port_id = device_details['port_id']
|
||||
profile = device_details['profile']
|
||||
spoofcheck = device_details.get('port_security_enabled', True)
|
||||
if self.treat_device(device,
|
||||
profile.get('pci_slot'),
|
||||
device_details['admin_state_up'],
|
||||
spoofcheck):
|
||||
if self.treat_device(
|
||||
device,
|
||||
profile.get('pci_slot'),
|
||||
device_details['admin_state_up'],
|
||||
spoofcheck,
|
||||
device_details['propagate_uplink_status']):
|
||||
if device_details['admin_state_up']:
|
||||
devices_up.add(device)
|
||||
else:
|
||||
|
42
neutron/plugins/ml2/extensions/uplink_status_propagation.py
Normal file
42
neutron/plugins/ml2/extensions/uplink_status_propagation.py
Normal file
@ -0,0 +1,42 @@
|
||||
# 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 uplink_status_propagation as usp
|
||||
from neutron_lib.plugins.ml2 import api
|
||||
from oslo_log import log as logging
|
||||
|
||||
from neutron.db import uplink_status_propagation_db as usp_db
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UplinkStatusPropagationExtensionDriver(
|
||||
api.ExtensionDriver, usp_db.UplinkStatusPropagationMixin):
|
||||
|
||||
_supported_extension_alias = 'uplink-status-propagation'
|
||||
|
||||
def initialize(self):
|
||||
LOG.info("UplinkStatusPropagationExtensionDriver initialization "
|
||||
"complete")
|
||||
|
||||
@property
|
||||
def extension_alias(self):
|
||||
return self._supported_extension_alias
|
||||
|
||||
def process_create_port(self, context, data, result):
|
||||
# Create the port extension attributes.
|
||||
if usp.PROPAGATE_UPLINK_STATUS in data:
|
||||
self._process_create_port(context, data, result)
|
||||
|
||||
def extend_port_dict(self, session, db_data, result):
|
||||
self._extend_port_dict(result, db_data)
|
@ -16,6 +16,7 @@
|
||||
from neutron_lib.agent import topics
|
||||
from neutron_lib.api.definitions import port_security as psec
|
||||
from neutron_lib.api.definitions import portbindings
|
||||
from neutron_lib.api.definitions import uplink_status_propagation as usp
|
||||
from neutron_lib.callbacks import resources
|
||||
from neutron_lib import constants as n_const
|
||||
from neutron_lib.plugins import directory
|
||||
@ -156,7 +157,9 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin):
|
||||
'port_security_enabled': port.get(psec.PORTSECURITY, True),
|
||||
'qos_policy_id': port.get(qos_consts.QOS_POLICY_ID),
|
||||
'network_qos_policy_id': network_qos_policy_id,
|
||||
'profile': port[portbindings.PROFILE]}
|
||||
'profile': port[portbindings.PROFILE],
|
||||
'propagate_uplink_status': port.get(
|
||||
usp.PROPAGATE_UPLINK_STATUS, False)}
|
||||
LOG.debug("Returning: %s", entry)
|
||||
return entry
|
||||
|
||||
|
@ -98,6 +98,7 @@ case $VENV in
|
||||
load_rc_hook disable_dvr_tests
|
||||
fi
|
||||
load_conf_hook quotas
|
||||
load_rc_hook uplink_status_propagation
|
||||
load_rc_hook dns
|
||||
load_rc_hook qos
|
||||
load_rc_hook segments
|
||||
|
@ -58,3 +58,4 @@ NETWORK_API_EXTENSIONS+=",standard-attr-tag"
|
||||
NETWORK_API_EXTENSIONS+=",subnet_allocation"
|
||||
NETWORK_API_EXTENSIONS+=",trunk"
|
||||
NETWORK_API_EXTENSIONS+=",trunk-details"
|
||||
NETWORK_API_EXTENSIONS+=",uplink-status-propagation"
|
||||
|
1
neutron/tests/contrib/hooks/uplink_status_propagation
Normal file
1
neutron/tests/contrib/hooks/uplink_status_propagation
Normal file
@ -0,0 +1 @@
|
||||
enable_service neutron-uplink-status-propagation
|
@ -400,7 +400,8 @@ class NeutronDbPluginV2TestCase(testlib_api.WebTestCase):
|
||||
|
||||
for arg in (('admin_state_up', 'device_id',
|
||||
'mac_address', 'name', 'fixed_ips',
|
||||
'tenant_id', 'device_owner', 'security_groups') +
|
||||
'tenant_id', 'device_owner', 'security_groups',
|
||||
'propagate_uplink_status') +
|
||||
(arg_list or ())):
|
||||
# Arg must be present
|
||||
if arg in kwargs:
|
||||
|
@ -0,0 +1,71 @@
|
||||
# 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 uplink_status_propagation as apidef
|
||||
|
||||
from neutron.db import _resource_extend as resource_extend
|
||||
from neutron.db import db_base_plugin_v2
|
||||
from neutron.db import uplink_status_propagation_db as usp_db
|
||||
from neutron.tests.unit.db import test_db_base_plugin_v2
|
||||
|
||||
|
||||
class UplinkStatusPropagationExtensionTestPlugin(
|
||||
db_base_plugin_v2.NeutronDbPluginV2,
|
||||
usp_db.UplinkStatusPropagationMixin):
|
||||
"""Test plugin to mixin the uplink status propagation extension.
|
||||
"""
|
||||
|
||||
supported_extension_aliases = [apidef.ALIAS]
|
||||
|
||||
@staticmethod
|
||||
@resource_extend.extends([apidef.COLLECTION_NAME])
|
||||
def _extend_network_project_default(port_res, port_db):
|
||||
return usp_db.UplinkStatusPropagationMixin._extend_port_dict(
|
||||
port_res, port_db)
|
||||
|
||||
def create_port(self, context, port):
|
||||
with context.session.begin(subtransactions=True):
|
||||
new_port = super(UplinkStatusPropagationExtensionTestPlugin,
|
||||
self).create_port(context, port)
|
||||
# Update the propagate_uplink_status in the database
|
||||
p = port['port']
|
||||
if 'propagate_uplink_status' not in p:
|
||||
p['propagate_uplink_status'] = False
|
||||
self._process_create_port(context, p, new_port)
|
||||
return new_port
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class UplinkStatusPropagationExtensionTestCase(
|
||||
test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
|
||||
"""Test API extension propagate_uplink_status attributes.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
plugin = ('neutron.tests.unit.extensions.test_uplink_status_'
|
||||
'propagation.UplinkStatusPropagationExtensionTestPlugin')
|
||||
super(UplinkStatusPropagationExtensionTestCase,
|
||||
self).setUp(plugin=plugin)
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_create_port_propagate_uplink_status(
|
||||
self, propagate_uplink_status):
|
||||
name = 'propagate_uplink_status'
|
||||
keys = [('name', name), ('admin_state_up', True),
|
||||
('status', self.port_create_status),
|
||||
('propagate_uplink_status', propagate_uplink_status)]
|
||||
with self.port(name=name,
|
||||
propagate_uplink_status=propagate_uplink_status
|
||||
) as port:
|
||||
for k, v in keys:
|
||||
self.assertEqual(v, port['port'][k])
|
@ -0,0 +1,33 @@
|
||||
# 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 uplink_status_propagation
|
||||
from neutron.tests.unit.objects import test_base as obj_test_base
|
||||
from neutron.tests.unit import testlib_api
|
||||
|
||||
|
||||
class UplinkStatusPropagationIfaceObjectTestCase(
|
||||
obj_test_base.BaseObjectIfaceTestCase):
|
||||
|
||||
_test_class = uplink_status_propagation.PortUplinkStatusPropagation
|
||||
|
||||
|
||||
class UplinkStatusPropagationDbObjectTestCase(
|
||||
obj_test_base.BaseDbObjectTestCase,
|
||||
testlib_api.SqlTestCase):
|
||||
|
||||
_test_class = uplink_status_propagation.PortUplinkStatusPropagation
|
||||
|
||||
def setUp(self):
|
||||
super(UplinkStatusPropagationDbObjectTestCase, self).setUp()
|
||||
self.update_obj_fields(
|
||||
{'port_id': lambda: self._create_test_port_id()})
|
@ -69,6 +69,7 @@ object_data = {
|
||||
'PortDNS': '1.1-c5ca2dc172bdd5fafee3fc986d1d7023',
|
||||
'PortForwarding': '1.1-db61273978c497239be5389a8aeb1c61',
|
||||
'PortSecurity': '1.0-b30802391a87945ee9c07582b4ff95e3',
|
||||
'PortUplinkStatusPropagation': '1.0-3cfb3f7da716ca9687e4f04ca72b081d',
|
||||
'ProviderResourceAssociation': '1.0-05ab2d5a3017e5ce9dd381328f285f34',
|
||||
'ProvisioningBlock': '1.0-c19d6d05bfa8143533471c1296066125',
|
||||
'QosBandwidthLimitRule': '1.3-51b662b12a8d1dfa89288d826c6d26d3',
|
||||
|
@ -158,27 +158,38 @@ class TestESwitchManagerApi(base.BaseTestCase):
|
||||
self.assertIn(devices_info['p6p1'][0], list(result))
|
||||
self.assertIn(devices_info['p6p2'][0], list(result))
|
||||
|
||||
def test_get_device_status_true(self):
|
||||
def test_get_device_status_enable(self):
|
||||
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
|
||||
"eswitch_manager.EmbSwitch.get_pci_device",
|
||||
return_value=self.ASSIGNED_MAC),\
|
||||
mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
|
||||
"eswitch_manager.EmbSwitch.get_device_state",
|
||||
return_value=True):
|
||||
return_value='enable'):
|
||||
result = self.eswitch_mgr.get_device_state(self.ASSIGNED_MAC,
|
||||
self.PCI_SLOT)
|
||||
self.assertTrue(result)
|
||||
self.assertEqual('enable', result)
|
||||
|
||||
def test_get_device_status_false(self):
|
||||
def test_get_device_status_disable(self):
|
||||
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
|
||||
"eswitch_manager.EmbSwitch.get_pci_device",
|
||||
return_value=self.ASSIGNED_MAC),\
|
||||
mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
|
||||
"eswitch_manager.EmbSwitch.get_device_state",
|
||||
return_value=False):
|
||||
return_value='disable'):
|
||||
result = self.eswitch_mgr.get_device_state(self.ASSIGNED_MAC,
|
||||
self.PCI_SLOT)
|
||||
self.assertFalse(result)
|
||||
self.assertEqual('disable', result)
|
||||
|
||||
def test_get_device_status_auto(self):
|
||||
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
|
||||
"eswitch_manager.EmbSwitch.get_pci_device",
|
||||
return_value=self.ASSIGNED_MAC),\
|
||||
mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
|
||||
"eswitch_manager.EmbSwitch.get_device_state",
|
||||
return_value='auto'):
|
||||
result = self.eswitch_mgr.get_device_state(self.ASSIGNED_MAC,
|
||||
self.PCI_SLOT)
|
||||
self.assertEqual('auto', result)
|
||||
|
||||
def test_get_device_status_mismatch(self):
|
||||
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
|
||||
@ -186,7 +197,7 @@ class TestESwitchManagerApi(base.BaseTestCase):
|
||||
return_value=self.ASSIGNED_MAC),\
|
||||
mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
|
||||
"eswitch_manager.EmbSwitch.get_device_state",
|
||||
return_value=True):
|
||||
return_value='enable'):
|
||||
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
|
||||
"eswitch_manager.LOG.warning") as log_mock:
|
||||
result = self.eswitch_mgr.get_device_state(self.WRONG_MAC,
|
||||
@ -195,7 +206,7 @@ class TestESwitchManagerApi(base.BaseTestCase):
|
||||
'%(device_mac)s - %(pci_slot)s',
|
||||
{'pci_slot': self.PCI_SLOT,
|
||||
'device_mac': self.WRONG_MAC})
|
||||
self.assertFalse(result)
|
||||
self.assertEqual('disable', result)
|
||||
|
||||
def test_set_device_status(self):
|
||||
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
|
||||
@ -204,7 +215,7 @@ class TestESwitchManagerApi(base.BaseTestCase):
|
||||
mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
|
||||
"eswitch_manager.EmbSwitch.set_device_state"):
|
||||
self.eswitch_mgr.set_device_state(self.ASSIGNED_MAC,
|
||||
self.PCI_SLOT, True)
|
||||
self.PCI_SLOT, True, False)
|
||||
|
||||
def test_set_device_max_rate(self):
|
||||
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
|
||||
@ -241,7 +252,7 @@ class TestESwitchManagerApi(base.BaseTestCase):
|
||||
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
|
||||
"eswitch_manager.LOG.warning") as log_mock:
|
||||
self.eswitch_mgr.set_device_state(self.WRONG_MAC,
|
||||
self.PCI_SLOT, True)
|
||||
self.PCI_SLOT, True, False)
|
||||
log_mock.assert_called_with('device pci mismatch: '
|
||||
'%(device_mac)s - %(pci_slot)s',
|
||||
{'pci_slot': self.PCI_SLOT,
|
||||
@ -463,7 +474,7 @@ class TestEmbSwitch(base.BaseTestCase):
|
||||
"PciDeviceIPWrapper.set_vf_state"):
|
||||
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
|
||||
"pci_lib.LOG.warning") as log_mock:
|
||||
self.emb_switch.set_device_state(self.PCI_SLOT, True)
|
||||
self.emb_switch.set_device_state(self.PCI_SLOT, True, False)
|
||||
self.assertEqual(0, log_mock.call_count)
|
||||
|
||||
def test_set_device_state_fail(self):
|
||||
@ -471,7 +482,7 @@ class TestEmbSwitch(base.BaseTestCase):
|
||||
"PciDeviceIPWrapper.set_vf_state"):
|
||||
self.assertRaises(exc.InvalidPciSlotError,
|
||||
self.emb_switch.set_device_state,
|
||||
self.WRONG_PCI_SLOT, True)
|
||||
self.WRONG_PCI_SLOT, True, False)
|
||||
|
||||
def test_set_device_spoofcheck_ok(self):
|
||||
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib."
|
||||
|
@ -81,14 +81,14 @@ class TestPciLib(base.BaseTestCase):
|
||||
"_as_root") as mock_as_root:
|
||||
mock_as_root.return_value = self.VF_LINK_SHOW
|
||||
result = self.pci_wrapper.get_vf_state(self.VF_INDEX)
|
||||
self.assertTrue(result)
|
||||
self.assertEqual('enable', result)
|
||||
|
||||
def test_get_vf_state_disable(self):
|
||||
with mock.patch.object(self.pci_wrapper,
|
||||
"_as_root") as mock_as_root:
|
||||
mock_as_root.return_value = self.VF_LINK_SHOW
|
||||
result = self.pci_wrapper.get_vf_state(self.VF_INDEX_DISABLE)
|
||||
self.assertFalse(result)
|
||||
self.assertEqual('disable', result)
|
||||
|
||||
def test_get_vf_state_fail(self):
|
||||
with mock.patch.object(self.pci_wrapper,
|
||||
|
@ -232,6 +232,7 @@ class TestSriovAgent(base.BaseTestCase):
|
||||
'port_id': 'port123',
|
||||
'network_id': 'net123',
|
||||
'admin_state_up': True,
|
||||
'propagate_uplink_status': True,
|
||||
'network_type': 'vlan',
|
||||
'segmentation_id': 100,
|
||||
'profile': {'pci_slot': '1:2:3.0'},
|
||||
@ -255,6 +256,7 @@ class TestSriovAgent(base.BaseTestCase):
|
||||
'port_id': 'port123',
|
||||
'network_id': 'net123',
|
||||
'admin_state_up': True,
|
||||
'propagate_uplink_status': False,
|
||||
'network_type': 'vlan',
|
||||
'segmentation_id': 100,
|
||||
'profile': {'pci_slot': SLOT1},
|
||||
@ -264,6 +266,7 @@ class TestSriovAgent(base.BaseTestCase):
|
||||
'port_id': 'port124',
|
||||
'network_id': 'net123',
|
||||
'admin_state_up': True,
|
||||
'propagate_uplink_status': False,
|
||||
'network_type': 'vlan',
|
||||
'segmentation_id': 100,
|
||||
'profile': {'pci_slot': SLOT2},
|
||||
@ -299,6 +302,7 @@ class TestSriovAgent(base.BaseTestCase):
|
||||
'port_id': 'port123',
|
||||
'network_id': 'net123',
|
||||
'admin_state_up': True,
|
||||
'propagate_uplink_status': False,
|
||||
'network_type': 'vlan',
|
||||
'segmentation_id': 100,
|
||||
'profile': {'pci_slot': '1:2:3.0'},
|
||||
@ -319,7 +323,7 @@ class TestSriovAgent(base.BaseTestCase):
|
||||
agent.eswitch_mgr.set_device_state.assert_called_with(
|
||||
'aa:bb:cc:dd:ee:ff',
|
||||
'1:2:3.0',
|
||||
True)
|
||||
True, False)
|
||||
agent.eswitch_mgr.set_device_spoofcheck.assert_called_with(
|
||||
'aa:bb:cc:dd:ee:ff',
|
||||
'1:2:3.0',
|
||||
@ -337,6 +341,7 @@ class TestSriovAgent(base.BaseTestCase):
|
||||
'port_id': 'port123',
|
||||
'network_id': 'net123',
|
||||
'admin_state_up': True,
|
||||
'propagate_uplink_status': False,
|
||||
'network_type': 'vlan',
|
||||
'segmentation_id': 100,
|
||||
'profile': {'pci_slot': '1:2:3.0'},
|
||||
@ -346,6 +351,7 @@ class TestSriovAgent(base.BaseTestCase):
|
||||
'port_id': 'port321',
|
||||
'network_id': 'net123',
|
||||
'admin_state_up': True,
|
||||
'propagate_uplink_status': False,
|
||||
'network_type': 'vlan',
|
||||
'segmentation_id': 100,
|
||||
'profile': {'pci_slot': '1:2:3.0'},
|
||||
@ -364,8 +370,8 @@ class TestSriovAgent(base.BaseTestCase):
|
||||
calls = [mock.call('aa:bb:cc:dd:ee:ff', '1:2:3.0'),
|
||||
mock.call('11:22:33:44:55:66', '1:2:3.0')]
|
||||
agent.eswitch_mgr.device_exists.assert_has_calls(calls, any_order=True)
|
||||
calls = [mock.call('aa:bb:cc:dd:ee:ff', '1:2:3.0', True),
|
||||
mock.call('11:22:33:44:55:66', '1:2:3.0', True)]
|
||||
calls = [mock.call('aa:bb:cc:dd:ee:ff', '1:2:3.0', True, False),
|
||||
mock.call('11:22:33:44:55:66', '1:2:3.0', True, False)]
|
||||
agent.eswitch_mgr.set_device_state.assert_has_calls(calls,
|
||||
any_order=True)
|
||||
calls = [mock.call('aa:bb:cc:dd:ee:ff', '1:2:3.0', False),
|
||||
@ -385,6 +391,7 @@ class TestSriovAgent(base.BaseTestCase):
|
||||
'port_id': 'port123',
|
||||
'network_id': 'net123',
|
||||
'admin_state_up': True,
|
||||
'propagate_uplink_status': False,
|
||||
'network_type': 'vlan',
|
||||
'segmentation_id': 100,
|
||||
'profile': {'pci_slot': '1:2:3.0'},
|
||||
@ -394,6 +401,7 @@ class TestSriovAgent(base.BaseTestCase):
|
||||
'port_id': 'port321',
|
||||
'network_id': 'net123',
|
||||
'admin_state_up': False,
|
||||
'propagate_uplink_status': False,
|
||||
'network_type': 'vlan',
|
||||
'segmentation_id': 100,
|
||||
'profile': {'pci_slot': '1:2:3.0'},
|
||||
@ -412,8 +420,8 @@ class TestSriovAgent(base.BaseTestCase):
|
||||
calls = [mock.call('aa:bb:cc:dd:ee:ff', '1:2:3.0'),
|
||||
mock.call('11:22:33:44:55:66', '1:2:3.0')]
|
||||
agent.eswitch_mgr.device_exists.assert_has_calls(calls, any_order=True)
|
||||
calls = [mock.call('aa:bb:cc:dd:ee:ff', '1:2:3.0', True),
|
||||
mock.call('11:22:33:44:55:66', '1:2:3.0', False)]
|
||||
calls = [mock.call('aa:bb:cc:dd:ee:ff', '1:2:3.0', True, False),
|
||||
mock.call('11:22:33:44:55:66', '1:2:3.0', False, False)]
|
||||
agent.eswitch_mgr.set_device_state.assert_has_calls(calls,
|
||||
any_order=True)
|
||||
calls = [mock.call('aa:bb:cc:dd:ee:ff', '1:2:3.0', False),
|
||||
@ -465,6 +473,7 @@ class TestSriovAgent(base.BaseTestCase):
|
||||
'port_id': 'port123',
|
||||
'network_id': 'net123',
|
||||
'admin_state_up': False,
|
||||
'propagate_uplink_status': False,
|
||||
'network_type': 'vlan',
|
||||
'segmentation_id': 100,
|
||||
'profile': {'pci_slot': '1:2:3.0'},
|
||||
@ -493,6 +502,7 @@ class TestSriovAgent(base.BaseTestCase):
|
||||
'network_type': 'vlan',
|
||||
'segmentation_id': 100,
|
||||
'profile': {'pci_slot': '1:2:3.0'},
|
||||
'propagate_uplink_status': False,
|
||||
'physical_network': 'physnet1'}
|
||||
agent.plugin_rpc = mock.Mock()
|
||||
agent.plugin_rpc.get_devices_details_list.return_value = [mock_details]
|
||||
|
@ -0,0 +1,70 @@
|
||||
# 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 mock
|
||||
|
||||
from neutron_lib.api.definitions import port as port_def
|
||||
from neutron_lib.plugins import directory
|
||||
from oslo_config import cfg
|
||||
|
||||
from neutron.plugins.ml2.extensions import uplink_status_propagation as usp
|
||||
from neutron.tests.unit.plugins.ml2 import test_plugin
|
||||
|
||||
|
||||
class UplinkStatusPropagationML2ExtDriverTestCase(
|
||||
test_plugin.Ml2PluginV2TestCase):
|
||||
|
||||
_extension_drivers = ['uplink_status_propagation']
|
||||
|
||||
def setUp(self):
|
||||
cfg.CONF.set_override('extension_drivers',
|
||||
self._extension_drivers,
|
||||
group='ml2')
|
||||
super(UplinkStatusPropagationML2ExtDriverTestCase, self).setUp()
|
||||
self.plugin = directory.get_plugin()
|
||||
|
||||
def test_extend_port_dict_no_project_default(self):
|
||||
for db_data in ({'propagate_uplink_status': None}, {}):
|
||||
response_data = {}
|
||||
session = mock.Mock()
|
||||
|
||||
driver = usp.UplinkStatusPropagationExtensionDriver()
|
||||
driver.extend_port_dict(session, db_data, response_data)
|
||||
self.assertFalse(response_data['propagate_uplink_status'])
|
||||
|
||||
def test_show_port_has_propagate_uplink_status(self):
|
||||
with self.port(propagate_uplink_status=True) as port:
|
||||
req = self.new_show_request(port_def.COLLECTION_NAME,
|
||||
port['port']['id'],
|
||||
self.fmt)
|
||||
n = self.deserialize(self.fmt, req.get_response(self.api))
|
||||
self.assertTrue(n['port']['propagate_uplink_status'])
|
||||
|
||||
def test_port_create_propagate_uplink_status(self):
|
||||
with self.network() as n:
|
||||
args = {'port':
|
||||
{'name': 'test',
|
||||
'network_id': n['network']['id'],
|
||||
'tenant_id': n['network']['id'],
|
||||
'device_id': '',
|
||||
'device_owner': '',
|
||||
'fixed_ips': '',
|
||||
'propagate_uplink_status': True,
|
||||
'admin_state_up': True,
|
||||
'status': 'ACTIVE'}}
|
||||
port = None
|
||||
try:
|
||||
port = self.plugin.create_port(self.context, args)
|
||||
finally:
|
||||
if port:
|
||||
self.plugin.delete_port(self.context, port['id'])
|
||||
self.assertTrue(port['propagate_uplink_status'])
|
@ -0,0 +1,15 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Introduce the attribute ``propagate_uplink_status`` to ports.
|
||||
Right now, the SRIOV mechanism driver leverages this attribute to decide
|
||||
if the VF link should follow the state of the PF.
|
||||
For example, if the PF is down, the VF link state is automatically
|
||||
set to down as well.
|
||||
Operators can turn on this feature via the configuration option::
|
||||
|
||||
[ml2]
|
||||
extension_drivers = uplink_status_propagation
|
||||
|
||||
The API extension ``uplink_status_propagation`` is introduced to indicate
|
||||
if this feature is turned on.
|
@ -100,6 +100,7 @@ neutron.ml2.extension_drivers =
|
||||
dns = neutron.plugins.ml2.extensions.dns_integration:DNSExtensionDriverML2
|
||||
data_plane_status = neutron.plugins.ml2.extensions.data_plane_status:DataPlaneStatusExtensionDriver
|
||||
dns_domain_ports = neutron.plugins.ml2.extensions.dns_integration:DNSDomainPortsExtensionDriver
|
||||
uplink_status_propagation = neutron.plugins.ml2.extensions.uplink_status_propagation:UplinkStatusPropagationExtensionDriver
|
||||
neutron.ipam_drivers =
|
||||
fake = neutron.tests.unit.ipam.fake_driver:FakeDriver
|
||||
internal = neutron.ipam.drivers.neutrondb_ipam.driver:NeutronDbPool
|
||||
|
Loading…
Reference in New Issue
Block a user