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/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
|
||||||
|
@ -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)
|
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
|
||||||
|
@ -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):
|
||||||
|
@ -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:
|
||||||
|
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.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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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"
|
||||||
|
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',
|
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:
|
||||||
|
@ -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',
|
'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',
|
||||||
|
@ -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."
|
||||||
|
@ -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,
|
||||||
|
@ -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]
|
||||||
|
@ -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
|
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
|
||||||
|
Loading…
Reference in New Issue
Block a user