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:
Hongbin Lu 2018-06-01 22:46:52 +00:00
parent 4b7a070b3f
commit f0678b9b09
26 changed files with 486 additions and 48 deletions

View File

@ -0,0 +1,3 @@
function configure_uplink_status_propagation_extension {
neutron_ml2_extension_driver_add "uplink_status_propagation"
}

View File

@ -12,6 +12,7 @@ source $LIBDIR/segments
source $LIBDIR/trunk source $LIBDIR/trunk
source $LIBDIR/log source $LIBDIR/log
source $LIBDIR/fip_port_forwarding source $LIBDIR/fip_port_forwarding
source $LIBDIR/uplink_status_propagation
Q_BUILD_OVS_FROM_GIT=$(trueorfalse False Q_BUILD_OVS_FROM_GIT) Q_BUILD_OVS_FROM_GIT=$(trueorfalse False Q_BUILD_OVS_FROM_GIT)
@ -30,6 +31,9 @@ if [[ "$1" == "stack" ]]; then
fi fi
;; ;;
post-config) 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 if is_service_enabled q-flavors neutron-flavors; then
configure_flavors configure_flavors
fi fi

View File

@ -1 +1 @@
d72db3e25539 cada2437bf41

View File

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

View 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', )

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

View 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

View 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'}}

View File

@ -169,14 +169,15 @@ class EmbSwitch(object):
vf_index = self._get_vf_index(pci_slot) vf_index = self._get_vf_index(pci_slot)
return self.pci_dev_wrapper.get_vf_state(vf_index) 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. """Set device state.
@param pci_slot: Virtual Function address @param pci_slot: Virtual Function address
@param state: link state @param state: link state
""" """
vf_index = self._get_vf_index(pci_slot) 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): def set_device_rate(self, pci_slot, rate_type, rate_kbps):
"""Set device rate: rate (max_tx_rate), min_tx_rate """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): def get_device_state(self, device_mac, pci_slot):
"""Get device state. """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 device_mac: device mac
@param pci_slot: VF PCI slot @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) embedded_switch = self._get_emb_eswitch(device_mac, pci_slot)
if embedded_switch: if embedded_switch:
return embedded_switch.get_device_state(pci_slot) 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): def set_device_max_rate(self, device_mac, pci_slot, max_kbps):
"""Set device max rate """Set device max rate
@ -333,18 +334,21 @@ class ESwitchManager(object):
ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_MIN_TX_RATE, ip_link_support.IpLinkConstants.IP_LINK_CAPABILITY_MIN_TX_RATE,
min_kbps) 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 """Set device state
Sets the device state (up or down) Sets the device state (up or down)
@param device_mac: device mac @param device_mac: device mac
@param pci_slot: pci slot @param pci_slot: pci slot
@param admin_state_up: device admin state True/False @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) embedded_switch = self._get_emb_eswitch(device_mac, pci_slot)
if embedded_switch: if embedded_switch:
embedded_switch.set_device_state(pci_slot, 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): def set_device_spoofcheck(self, device_mac, pci_slot, enabled):
"""Set device spoofcheck """Set device spoofcheck

View File

@ -24,6 +24,12 @@ from neutron.plugins.ml2.drivers.mech_sriov.agent.common \
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class LinkState(object):
ENABLE = "enable"
DISABLE = "disable"
AUTO = "auto"
class PciDeviceIPWrapper(ip_lib.IPWrapper): class PciDeviceIPWrapper(ip_lib.IPWrapper):
"""Wrapper class for ip link commands. """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' IP_LINK_OP_NOT_SUPPORTED = 'RTNETLINK answers: Operation not supported'
class LinkState(object):
ENABLE = "enable"
DISABLE = "disable"
def __init__(self, dev_name): def __init__(self, dev_name):
super(PciDeviceIPWrapper, self).__init__() super(PciDeviceIPWrapper, self).__init__()
self.dev_name = dev_name self.dev_name = dev_name
@ -96,10 +98,9 @@ class PciDeviceIPWrapper(ip_lib.IPWrapper):
return vf_to_mac_mapping return vf_to_mac_mapping
def get_vf_state(self, vf_index): def get_vf_state(self, vf_index):
"""Get vf state {True/False} """Get vf state {enable/disable/auto}
@param vf_index: vf index @param vf_index: vf index
@todo: Handle "auto" state
""" """
try: try:
out = self._as_root([], "link", ("show", self.dev_name)) 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]) vf_details = self._parse_vf_link_show(vf_lines[0])
if vf_details: if vf_details:
state = vf_details.get("link-state", state = vf_details.get("link-state",
self.LinkState.DISABLE) LinkState.DISABLE)
if state != self.LinkState.DISABLE: if state in (LinkState.AUTO, LinkState.ENABLE):
return True return state
return False return LinkState.DISABLE
def set_vf_state(self, vf_index, state): def set_vf_state(self, vf_index, state, auto=False):
"""sets vf state. """sets vf state.
@param vf_index: vf index @param vf_index: vf index
@param state: required state {True/False} @param state: required state {True/False}
""" """
status_str = self.LinkState.ENABLE if state else \ if auto:
self.LinkState.DISABLE status_str = LinkState.AUTO
else:
status_str = LinkState.ENABLE if state else \
LinkState.DISABLE
self._set_feature(vf_index, "state", status_str) self._set_feature(vf_index, "state", status_str)
def set_vf_spoofcheck(self, vf_index, enabled): def set_vf_spoofcheck(self, vf_index, enabled):

View File

@ -240,7 +240,8 @@ class SriovNicSwitchAgent(object):
# If one of the above operations fails => resync with plugin # If one of the above operations fails => resync with plugin
return (resync_a | resync_b) 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): if self.eswitch_mgr.device_exists(device, pci_slot):
try: try:
self.eswitch_mgr.set_device_spoofcheck(device, pci_slot, self.eswitch_mgr.set_device_spoofcheck(device, pci_slot,
@ -253,7 +254,8 @@ class SriovNicSwitchAgent(object):
try: try:
self.eswitch_mgr.set_device_state(device, pci_slot, self.eswitch_mgr.set_device_state(device, pci_slot,
admin_state_up) admin_state_up,
propagate_uplink_state)
except exc.IpCommandOperationNotSupportedError: except exc.IpCommandOperationNotSupportedError:
LOG.warning("Device %s does not support state change", LOG.warning("Device %s does not support state change",
device) device)
@ -305,10 +307,12 @@ class SriovNicSwitchAgent(object):
port_id = device_details['port_id'] port_id = device_details['port_id']
profile = device_details['profile'] profile = device_details['profile']
spoofcheck = device_details.get('port_security_enabled', True) spoofcheck = device_details.get('port_security_enabled', True)
if self.treat_device(device, if self.treat_device(
profile.get('pci_slot'), device,
device_details['admin_state_up'], profile.get('pci_slot'),
spoofcheck): device_details['admin_state_up'],
spoofcheck,
device_details['propagate_uplink_status']):
if device_details['admin_state_up']: if device_details['admin_state_up']:
devices_up.add(device) devices_up.add(device)
else: else:

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

View File

@ -16,6 +16,7 @@
from neutron_lib.agent import topics from neutron_lib.agent import topics
from neutron_lib.api.definitions import port_security as psec from neutron_lib.api.definitions import port_security as psec
from neutron_lib.api.definitions import portbindings 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.callbacks import resources
from neutron_lib import constants as n_const from neutron_lib import constants as n_const
from neutron_lib.plugins import directory from neutron_lib.plugins import directory
@ -156,7 +157,9 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin):
'port_security_enabled': port.get(psec.PORTSECURITY, True), 'port_security_enabled': port.get(psec.PORTSECURITY, True),
'qos_policy_id': port.get(qos_consts.QOS_POLICY_ID), 'qos_policy_id': port.get(qos_consts.QOS_POLICY_ID),
'network_qos_policy_id': network_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) LOG.debug("Returning: %s", entry)
return entry return entry

View File

@ -98,6 +98,7 @@ case $VENV in
load_rc_hook disable_dvr_tests load_rc_hook disable_dvr_tests
fi fi
load_conf_hook quotas load_conf_hook quotas
load_rc_hook uplink_status_propagation
load_rc_hook dns load_rc_hook dns
load_rc_hook qos load_rc_hook qos
load_rc_hook segments load_rc_hook segments

View File

@ -58,3 +58,4 @@ NETWORK_API_EXTENSIONS+=",standard-attr-tag"
NETWORK_API_EXTENSIONS+=",subnet_allocation" NETWORK_API_EXTENSIONS+=",subnet_allocation"
NETWORK_API_EXTENSIONS+=",trunk" NETWORK_API_EXTENSIONS+=",trunk"
NETWORK_API_EXTENSIONS+=",trunk-details" NETWORK_API_EXTENSIONS+=",trunk-details"
NETWORK_API_EXTENSIONS+=",uplink-status-propagation"

View File

@ -0,0 +1 @@
enable_service neutron-uplink-status-propagation

View File

@ -400,7 +400,8 @@ class NeutronDbPluginV2TestCase(testlib_api.WebTestCase):
for arg in (('admin_state_up', 'device_id', for arg in (('admin_state_up', 'device_id',
'mac_address', 'name', 'fixed_ips', 'mac_address', 'name', 'fixed_ips',
'tenant_id', 'device_owner', 'security_groups') + 'tenant_id', 'device_owner', 'security_groups',
'propagate_uplink_status') +
(arg_list or ())): (arg_list or ())):
# Arg must be present # Arg must be present
if arg in kwargs: if arg in kwargs:

View File

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

View 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.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()})

View File

@ -69,6 +69,7 @@ object_data = {
'PortDNS': '1.1-c5ca2dc172bdd5fafee3fc986d1d7023', 'PortDNS': '1.1-c5ca2dc172bdd5fafee3fc986d1d7023',
'PortForwarding': '1.1-db61273978c497239be5389a8aeb1c61', 'PortForwarding': '1.1-db61273978c497239be5389a8aeb1c61',
'PortSecurity': '1.0-b30802391a87945ee9c07582b4ff95e3', 'PortSecurity': '1.0-b30802391a87945ee9c07582b4ff95e3',
'PortUplinkStatusPropagation': '1.0-3cfb3f7da716ca9687e4f04ca72b081d',
'ProviderResourceAssociation': '1.0-05ab2d5a3017e5ce9dd381328f285f34', 'ProviderResourceAssociation': '1.0-05ab2d5a3017e5ce9dd381328f285f34',
'ProvisioningBlock': '1.0-c19d6d05bfa8143533471c1296066125', 'ProvisioningBlock': '1.0-c19d6d05bfa8143533471c1296066125',
'QosBandwidthLimitRule': '1.3-51b662b12a8d1dfa89288d826c6d26d3', 'QosBandwidthLimitRule': '1.3-51b662b12a8d1dfa89288d826c6d26d3',

View File

@ -158,27 +158,38 @@ class TestESwitchManagerApi(base.BaseTestCase):
self.assertIn(devices_info['p6p1'][0], list(result)) self.assertIn(devices_info['p6p1'][0], list(result))
self.assertIn(devices_info['p6p2'][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." with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
"eswitch_manager.EmbSwitch.get_pci_device", "eswitch_manager.EmbSwitch.get_pci_device",
return_value=self.ASSIGNED_MAC),\ return_value=self.ASSIGNED_MAC),\
mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent." mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
"eswitch_manager.EmbSwitch.get_device_state", "eswitch_manager.EmbSwitch.get_device_state",
return_value=True): return_value='enable'):
result = self.eswitch_mgr.get_device_state(self.ASSIGNED_MAC, result = self.eswitch_mgr.get_device_state(self.ASSIGNED_MAC,
self.PCI_SLOT) 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." with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
"eswitch_manager.EmbSwitch.get_pci_device", "eswitch_manager.EmbSwitch.get_pci_device",
return_value=self.ASSIGNED_MAC),\ return_value=self.ASSIGNED_MAC),\
mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent." mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
"eswitch_manager.EmbSwitch.get_device_state", "eswitch_manager.EmbSwitch.get_device_state",
return_value=False): return_value='disable'):
result = self.eswitch_mgr.get_device_state(self.ASSIGNED_MAC, result = self.eswitch_mgr.get_device_state(self.ASSIGNED_MAC,
self.PCI_SLOT) 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): def test_get_device_status_mismatch(self):
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent." with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
@ -186,7 +197,7 @@ class TestESwitchManagerApi(base.BaseTestCase):
return_value=self.ASSIGNED_MAC),\ return_value=self.ASSIGNED_MAC),\
mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent." mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
"eswitch_manager.EmbSwitch.get_device_state", "eswitch_manager.EmbSwitch.get_device_state",
return_value=True): return_value='enable'):
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent." with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
"eswitch_manager.LOG.warning") as log_mock: "eswitch_manager.LOG.warning") as log_mock:
result = self.eswitch_mgr.get_device_state(self.WRONG_MAC, result = self.eswitch_mgr.get_device_state(self.WRONG_MAC,
@ -195,7 +206,7 @@ class TestESwitchManagerApi(base.BaseTestCase):
'%(device_mac)s - %(pci_slot)s', '%(device_mac)s - %(pci_slot)s',
{'pci_slot': self.PCI_SLOT, {'pci_slot': self.PCI_SLOT,
'device_mac': self.WRONG_MAC}) 'device_mac': self.WRONG_MAC})
self.assertFalse(result) self.assertEqual('disable', result)
def test_set_device_status(self): def test_set_device_status(self):
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent." 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." mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
"eswitch_manager.EmbSwitch.set_device_state"): "eswitch_manager.EmbSwitch.set_device_state"):
self.eswitch_mgr.set_device_state(self.ASSIGNED_MAC, 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): def test_set_device_max_rate(self):
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent." 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." with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
"eswitch_manager.LOG.warning") as log_mock: "eswitch_manager.LOG.warning") as log_mock:
self.eswitch_mgr.set_device_state(self.WRONG_MAC, 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: ' log_mock.assert_called_with('device pci mismatch: '
'%(device_mac)s - %(pci_slot)s', '%(device_mac)s - %(pci_slot)s',
{'pci_slot': self.PCI_SLOT, {'pci_slot': self.PCI_SLOT,
@ -463,7 +474,7 @@ class TestEmbSwitch(base.BaseTestCase):
"PciDeviceIPWrapper.set_vf_state"): "PciDeviceIPWrapper.set_vf_state"):
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent." with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent."
"pci_lib.LOG.warning") as log_mock: "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) self.assertEqual(0, log_mock.call_count)
def test_set_device_state_fail(self): def test_set_device_state_fail(self):
@ -471,7 +482,7 @@ class TestEmbSwitch(base.BaseTestCase):
"PciDeviceIPWrapper.set_vf_state"): "PciDeviceIPWrapper.set_vf_state"):
self.assertRaises(exc.InvalidPciSlotError, self.assertRaises(exc.InvalidPciSlotError,
self.emb_switch.set_device_state, self.emb_switch.set_device_state,
self.WRONG_PCI_SLOT, True) self.WRONG_PCI_SLOT, True, False)
def test_set_device_spoofcheck_ok(self): def test_set_device_spoofcheck_ok(self):
with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib." with mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.pci_lib."

View File

@ -81,14 +81,14 @@ class TestPciLib(base.BaseTestCase):
"_as_root") as mock_as_root: "_as_root") as mock_as_root:
mock_as_root.return_value = self.VF_LINK_SHOW mock_as_root.return_value = self.VF_LINK_SHOW
result = self.pci_wrapper.get_vf_state(self.VF_INDEX) result = self.pci_wrapper.get_vf_state(self.VF_INDEX)
self.assertTrue(result) self.assertEqual('enable', result)
def test_get_vf_state_disable(self): def test_get_vf_state_disable(self):
with mock.patch.object(self.pci_wrapper, with mock.patch.object(self.pci_wrapper,
"_as_root") as mock_as_root: "_as_root") as mock_as_root:
mock_as_root.return_value = self.VF_LINK_SHOW mock_as_root.return_value = self.VF_LINK_SHOW
result = self.pci_wrapper.get_vf_state(self.VF_INDEX_DISABLE) 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): def test_get_vf_state_fail(self):
with mock.patch.object(self.pci_wrapper, with mock.patch.object(self.pci_wrapper,

View File

@ -232,6 +232,7 @@ class TestSriovAgent(base.BaseTestCase):
'port_id': 'port123', 'port_id': 'port123',
'network_id': 'net123', 'network_id': 'net123',
'admin_state_up': True, 'admin_state_up': True,
'propagate_uplink_status': True,
'network_type': 'vlan', 'network_type': 'vlan',
'segmentation_id': 100, 'segmentation_id': 100,
'profile': {'pci_slot': '1:2:3.0'}, 'profile': {'pci_slot': '1:2:3.0'},
@ -255,6 +256,7 @@ class TestSriovAgent(base.BaseTestCase):
'port_id': 'port123', 'port_id': 'port123',
'network_id': 'net123', 'network_id': 'net123',
'admin_state_up': True, 'admin_state_up': True,
'propagate_uplink_status': False,
'network_type': 'vlan', 'network_type': 'vlan',
'segmentation_id': 100, 'segmentation_id': 100,
'profile': {'pci_slot': SLOT1}, 'profile': {'pci_slot': SLOT1},
@ -264,6 +266,7 @@ class TestSriovAgent(base.BaseTestCase):
'port_id': 'port124', 'port_id': 'port124',
'network_id': 'net123', 'network_id': 'net123',
'admin_state_up': True, 'admin_state_up': True,
'propagate_uplink_status': False,
'network_type': 'vlan', 'network_type': 'vlan',
'segmentation_id': 100, 'segmentation_id': 100,
'profile': {'pci_slot': SLOT2}, 'profile': {'pci_slot': SLOT2},
@ -299,6 +302,7 @@ class TestSriovAgent(base.BaseTestCase):
'port_id': 'port123', 'port_id': 'port123',
'network_id': 'net123', 'network_id': 'net123',
'admin_state_up': True, 'admin_state_up': True,
'propagate_uplink_status': False,
'network_type': 'vlan', 'network_type': 'vlan',
'segmentation_id': 100, 'segmentation_id': 100,
'profile': {'pci_slot': '1:2:3.0'}, 'profile': {'pci_slot': '1:2:3.0'},
@ -319,7 +323,7 @@ class TestSriovAgent(base.BaseTestCase):
agent.eswitch_mgr.set_device_state.assert_called_with( agent.eswitch_mgr.set_device_state.assert_called_with(
'aa:bb:cc:dd:ee:ff', 'aa:bb:cc:dd:ee:ff',
'1:2:3.0', '1:2:3.0',
True) True, False)
agent.eswitch_mgr.set_device_spoofcheck.assert_called_with( agent.eswitch_mgr.set_device_spoofcheck.assert_called_with(
'aa:bb:cc:dd:ee:ff', 'aa:bb:cc:dd:ee:ff',
'1:2:3.0', '1:2:3.0',
@ -337,6 +341,7 @@ class TestSriovAgent(base.BaseTestCase):
'port_id': 'port123', 'port_id': 'port123',
'network_id': 'net123', 'network_id': 'net123',
'admin_state_up': True, 'admin_state_up': True,
'propagate_uplink_status': False,
'network_type': 'vlan', 'network_type': 'vlan',
'segmentation_id': 100, 'segmentation_id': 100,
'profile': {'pci_slot': '1:2:3.0'}, 'profile': {'pci_slot': '1:2:3.0'},
@ -346,6 +351,7 @@ class TestSriovAgent(base.BaseTestCase):
'port_id': 'port321', 'port_id': 'port321',
'network_id': 'net123', 'network_id': 'net123',
'admin_state_up': True, 'admin_state_up': True,
'propagate_uplink_status': False,
'network_type': 'vlan', 'network_type': 'vlan',
'segmentation_id': 100, 'segmentation_id': 100,
'profile': {'pci_slot': '1:2:3.0'}, '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'), 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')] mock.call('11:22:33:44:55:66', '1:2:3.0')]
agent.eswitch_mgr.device_exists.assert_has_calls(calls, any_order=True) 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), 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)] mock.call('11:22:33:44:55:66', '1:2:3.0', True, False)]
agent.eswitch_mgr.set_device_state.assert_has_calls(calls, agent.eswitch_mgr.set_device_state.assert_has_calls(calls,
any_order=True) any_order=True)
calls = [mock.call('aa:bb:cc:dd:ee:ff', '1:2:3.0', False), 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', 'port_id': 'port123',
'network_id': 'net123', 'network_id': 'net123',
'admin_state_up': True, 'admin_state_up': True,
'propagate_uplink_status': False,
'network_type': 'vlan', 'network_type': 'vlan',
'segmentation_id': 100, 'segmentation_id': 100,
'profile': {'pci_slot': '1:2:3.0'}, 'profile': {'pci_slot': '1:2:3.0'},
@ -394,6 +401,7 @@ class TestSriovAgent(base.BaseTestCase):
'port_id': 'port321', 'port_id': 'port321',
'network_id': 'net123', 'network_id': 'net123',
'admin_state_up': False, 'admin_state_up': False,
'propagate_uplink_status': False,
'network_type': 'vlan', 'network_type': 'vlan',
'segmentation_id': 100, 'segmentation_id': 100,
'profile': {'pci_slot': '1:2:3.0'}, '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'), 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')] mock.call('11:22:33:44:55:66', '1:2:3.0')]
agent.eswitch_mgr.device_exists.assert_has_calls(calls, any_order=True) 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), 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)] mock.call('11:22:33:44:55:66', '1:2:3.0', False, False)]
agent.eswitch_mgr.set_device_state.assert_has_calls(calls, agent.eswitch_mgr.set_device_state.assert_has_calls(calls,
any_order=True) any_order=True)
calls = [mock.call('aa:bb:cc:dd:ee:ff', '1:2:3.0', False), 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', 'port_id': 'port123',
'network_id': 'net123', 'network_id': 'net123',
'admin_state_up': False, 'admin_state_up': False,
'propagate_uplink_status': False,
'network_type': 'vlan', 'network_type': 'vlan',
'segmentation_id': 100, 'segmentation_id': 100,
'profile': {'pci_slot': '1:2:3.0'}, 'profile': {'pci_slot': '1:2:3.0'},
@ -493,6 +502,7 @@ class TestSriovAgent(base.BaseTestCase):
'network_type': 'vlan', 'network_type': 'vlan',
'segmentation_id': 100, 'segmentation_id': 100,
'profile': {'pci_slot': '1:2:3.0'}, 'profile': {'pci_slot': '1:2:3.0'},
'propagate_uplink_status': False,
'physical_network': 'physnet1'} 'physical_network': 'physnet1'}
agent.plugin_rpc = mock.Mock() agent.plugin_rpc = mock.Mock()
agent.plugin_rpc.get_devices_details_list.return_value = [mock_details] agent.plugin_rpc.get_devices_details_list.return_value = [mock_details]

View File

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

View File

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

View File

@ -100,6 +100,7 @@ neutron.ml2.extension_drivers =
dns = neutron.plugins.ml2.extensions.dns_integration:DNSExtensionDriverML2 dns = neutron.plugins.ml2.extensions.dns_integration:DNSExtensionDriverML2
data_plane_status = neutron.plugins.ml2.extensions.data_plane_status:DataPlaneStatusExtensionDriver data_plane_status = neutron.plugins.ml2.extensions.data_plane_status:DataPlaneStatusExtensionDriver
dns_domain_ports = neutron.plugins.ml2.extensions.dns_integration:DNSDomainPortsExtensionDriver dns_domain_ports = neutron.plugins.ml2.extensions.dns_integration:DNSDomainPortsExtensionDriver
uplink_status_propagation = neutron.plugins.ml2.extensions.uplink_status_propagation:UplinkStatusPropagationExtensionDriver
neutron.ipam_drivers = neutron.ipam_drivers =
fake = neutron.tests.unit.ipam.fake_driver:FakeDriver fake = neutron.tests.unit.ipam.fake_driver:FakeDriver
internal = neutron.ipam.drivers.neutrondb_ipam.driver:NeutronDbPool internal = neutron.ipam.drivers.neutrondb_ipam.driver:NeutronDbPool