UCSM Driver retry Port Profile deletes

When VMs attached to a Port Profile are deleted, and the neutron
network associated with this Port Profile is deleted, the Port
Profile might not be deleted. That is because UCSM is unaware of the
deletion on the VMs until it syncs up the UCS Servers. During this
time Port Profiles will not be deleted by the UCSM which thinks they
are inuse. This fix adds a retry mechanism where the plugin keeps
track of Port Profiles that need to be deleted and retries every 10
minutes.

Change-Id: Iedc22d80f193d9b1482abdd6d68e7bda352dfe3f
Closes-Bug: #1476721
This commit is contained in:
Sandhya Dasu 2017-02-10 15:24:05 -05:00
parent 9d13d885de
commit 1a0e03c3dc
9 changed files with 258 additions and 35 deletions

View File

@ -50,13 +50,17 @@ else:
if NEUTRON_VERSION >= NEUTRON_OCATA_VERSION:
from neutron import context
from neutron.db.models import agent as agent_model
from neutron.db.models import l3 as l3_models
from neutron_lib.api import extensions
from neutron_lib.db import model_base
from neutron_lib.plugins import directory
try:
from neutron import context
except ImportError:
from neutron_lib import context
get_plugin = directory.get_plugin
n_c_attr_names = dir(n_c)
HasProject = model_base.HasProject

View File

@ -1 +1 @@
b29f1026b281
203b495958cf

View File

@ -0,0 +1,36 @@
# Copyright 2017 Cisco Systems, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Add Port Profile delete table for UCSM plugin
Revision ID: 203b495958cf
Revises: b29f1026b281
Create Date: 2017-01-03 16:25:03.426346
"""
# revision identifiers, used by Alembic.
revision = '203b495958cf'
down_revision = 'b29f1026b281'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.create_table('ml2_ucsm_delete_port_profiles',
sa.Column('profile_id', sa.String(length=64), nullable=False),
sa.Column('device_id', sa.String(length=64), nullable=False),
sa.PrimaryKeyConstraint('profile_id', 'device_id')
)

View File

@ -21,7 +21,6 @@ from networking_cisco._i18n import _
from networking_cisco.plugins.ml2.drivers.cisco.ucsm import constants as const
LOG = logging.getLogger(__name__)
DEPRECATION_MESSAGE = "This will be removed in the N cycle."
""" Cisco UCS Manager ML2 Mechanism driver specific configuration.
@ -337,10 +336,7 @@ class UcsmConfig(object):
self.multivlan_trunk_dict[key] = vlans
def get_sriov_multivlan_trunk_config(self, network):
if network in self.multivlan_trunk_dict:
return self.multivlan_trunk_dict[network]
else:
return None
return self.multivlan_trunk_dict.get(network, [])
def get_sriov_qos_policy(self, ucsm_ip):
if cfg.CONF.ml2_cisco_ucsm.sriov_qos_policy:

View File

@ -53,3 +53,6 @@ SP_TEMPLATE_PREFIX = "/ls-"
VNIC_TEMPLATE_PARENT_DN = "org-root/lan-conn-templ-"
VNIC_TEMPLATE_PREFIX = "/lan-conn-templ-"
TRANSPORT = "ethernet"
DEFAULT_PP_DELETE_TIME = (10 * 60)
MAX_PP_DELETE_RETRY_COUNT = 3

View File

@ -151,3 +151,27 @@ class UcsmDbModel(object):
vlan_id=vlan_id).delete()
except orm.exc.NoResultFound:
return
def has_port_profile_to_delete(self, profile_name, device_id):
"""Returns True if port profile delete table containes PP."""
count = self.session.query(ucsm_model.PortProfileDelete).filter_by(
profile_id=profile_name, device_id=device_id).count()
return count != 0
def add_port_profile_to_delete_table(self, profile_name, device_id):
"""Adds a port profile to the delete table."""
if not self.has_port_profile_to_delete(profile_name, device_id):
port_profile = ucsm_model.PortProfileDelete(
profile_id=profile_name, device_id=device_id)
with self.session.begin(subtransactions=True):
self.session.add(port_profile)
return port_profile
def get_all_port_profiles_to_delete(self):
return self.session.query(ucsm_model.PortProfileDelete).all()
def remove_port_profile_to_delete(self, profile_name, device_id):
"""Removes port profile to be deleted from table."""
with self.session.begin(subtransactions=True):
self.session.query(ucsm_model.PortProfileDelete).filter_by(
profile_id=profile_name, device_id=device_id).delete()

View File

@ -54,3 +54,13 @@ class VnicTemplate(bc.model_base.BASEV2):
device_id = sa.Column(sa.String(64), nullable=False, primary_key=True)
physnet = sa.Column(sa.String(32), nullable=False)
updated_on_ucs = sa.Column(sa.Boolean(), nullable=False)
class PortProfileDelete(bc.model_base.BASEV2):
"""Port profiles to be deleted on the UCS Manager."""
__tablename__ = 'ml2_ucsm_delete_port_profiles'
profile_id = sa.Column(sa.String(64), nullable=False, primary_key=True)
device_id = sa.Column(sa.String(64), nullable=False, primary_key=True)

View File

@ -13,20 +13,23 @@
# License for the specific language governing permissions and limitations
# under the License.
from collections import defaultdict
import six
import sys
from threading import Timer
from contextlib import contextmanager
from neutron.extensions import portbindings
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import importutils
from networking_cisco._i18n import _LE, _LI, _LW
from neutron.extensions import portbindings
from networking_cisco.plugins.ml2.drivers.cisco.ucsm import config
from networking_cisco.plugins.ml2.drivers.cisco.ucsm import constants as const
from networking_cisco.plugins.ml2.drivers.cisco.ucsm import exceptions as cexc
from networking_cisco.plugins.ml2.drivers.cisco.ucsm import ucsm_db
LOG = logging.getLogger(__name__)
@ -42,10 +45,35 @@ class CiscoUcsmDriver(object):
portbindings.VNIC_MACVTAP]
self.supported_pci_devs = config.parse_pci_vendor_config()
self.ucsm_conf = config.UcsmConfig()
self.ucsm_db = ucsm_db.UcsmDbModel()
self.ucsm_host_dict = {}
self.ucsm_sp_dict = {}
self._disable_ssl_cert_check()
self._create_host_and_sp_dicts_from_config()
Timer(const.DEFAULT_PP_DELETE_TIME,
self._delayed_delete_port_profile, ()).start()
LOG.debug('Starting periodic Port Profile delete timer for %d',
const.DEFAULT_PP_DELETE_TIME)
def _disable_ssl_cert_check(self):
"""Disable SSL certificate checks.
Starting from Python version 2.7.9, SSL class performs certificate
checks by default. UcsSdk is currently unable to handle these
SSL certificate checks. This method disables this behavior. Once
support for SSL certificates is added to ucssdk, this method can be
removed.
"""
if sys.version_info >= (2, 6):
from functools import partial
import ssl
ssl.wrap_socket = partial(ssl.wrap_socket,
ssl_version=ssl.PROTOCOL_TLSv1)
if sys.version_info >= (2, 7, 9):
ssl._create_default_https_context = (
ssl._create_unverified_context)
def check_vnic_type_and_vendor_info(self, vnic_type, profile):
"""Checks if this vnic_type and vendor device info are supported.
@ -256,6 +284,10 @@ class CiscoUcsmDriver(object):
profile_name + const.CLIENT_PROFILE_PATH_PREFIX +
cl_profile_name)
# Remove this Port Profile from the delete DB table if it was
# addded there due to a previous delete.
self.ucsm_db.remove_port_profile_to_delete(profile_name, ucsm_ip)
# Check if direct or macvtap mode
if vnic_type == portbindings.VNIC_DIRECT:
port_mode = const.HIGH_PERF
@ -642,36 +674,70 @@ class CiscoUcsmDriver(object):
raise cexc.UcsmConfigFailed(config=vlan_id,
ucsm_ip=ucsm_ip, exc=e)
def _delete_port_profile(self, handle, port_profile, ucsm_ip):
def _delayed_delete_port_profile(self):
pp_delete_dict = defaultdict(list)
Timer(const.DEFAULT_PP_DELETE_TIME,
self._delayed_delete_port_profile, ()).start()
all_pps = self.ucsm_db.get_all_port_profiles_to_delete()
for pp in all_pps:
pp_delete_dict[pp.device_id].append(pp.profile_id)
# Connect to each UCSM IP and try to delete Port profiles
for ucsm_ip in pp_delete_dict.keys():
with self.ucsm_connect_disconnect(ucsm_ip) as handle:
for pp in pp_delete_dict.get(ucsm_ip):
LOG.debug('Deleting PP %s from UCSM %s', pp,
ucsm_ip)
try:
self._delete_port_profile_from_ucsm(handle,
pp, ucsm_ip)
# Remove this Port Profile from the delete DB table
# if it was addded there due to a previous delete.
LOG.debug('Removing PP %s from delete table after '
'successful delete', pp)
self.ucsm_db.remove_port_profile_to_delete(pp, ucsm_ip)
except Exception:
#do nothing
LOG.debug('Could not delete PP %s from UCSM %s',
pp, ucsm_ip)
def _delete_port_profile_from_ucsm(self, handle, port_profile, ucsm_ip):
"""Deletes Port Profile from UCS Manager."""
port_profile_dest = (const.PORT_PROFILESETDN + const.VNIC_PATH_PREFIX +
port_profile)
handle.StartTransaction()
try:
handle.StartTransaction()
# Find port profile on the UCS Manager
p_profile = handle.GetManagedObject(
None,
self.ucsmsdk.VnicProfile.ClassId(),
{self.ucsmsdk.VnicProfile.NAME: port_profile,
self.ucsmsdk.VnicProfile.DN: port_profile_dest})
if not p_profile:
LOG.warning(_LW('UCS Manager network driver did not find '
'Port Profile %s to delete.'),
port_profile)
return
# Find port profile on the UCS Manager
p_profile = handle.GetManagedObject(
None,
self.ucsmsdk.VnicProfile.ClassId(),
{self.ucsmsdk.VnicProfile.NAME: port_profile,
self.ucsmsdk.VnicProfile.DN: port_profile_dest})
if p_profile:
handle.RemoveManagedObject(p_profile)
handle.CompleteTransaction()
else:
LOG.warning(_LW('UCS Manager network driver did not find '
'Port Profile %s to delete.'),
port_profile)
handle.CompleteTransaction()
def _delete_port_profile(self, handle, port_profile, ucsm_ip):
"""Calls method to delete Port Profile from UCS Manager.
If exception is raised by UCSM, then the PP is added to
a DB table. The delete timer thread, tried to delete all
PPs added to this table when it wakes up.
"""
try:
self._delete_port_profile_from_ucsm(handle, port_profile, ucsm_ip)
except Exception as e:
# Raise a Neutron exception. Include a description of
# the original exception.
raise cexc.UcsmConfigDeleteFailed(config=port_profile,
ucsm_ip=ucsm_ip,
exc=e)
# Add the Port Profile that we could not delete to the Port Profile
# delete table. A periodic task will attempt to delete it.
LOG.debug('Received Port Profile delete exception %s', e)
self.ucsm_db.add_port_profile_to_delete_table(port_profile,
ucsm_ip)
def _remove_vlan_from_all_service_profiles(self, handle, vlan_id, ucsm_ip):
"""Deletes VLAN Profile config from server's ethernet ports."""
@ -790,7 +856,7 @@ class CiscoUcsmDriver(object):
def _remove_vlan_from_vnic_templates(self, handle, vlan_id, ucsm_ip):
"""Removes VLAN from all VNIC templates that have it enabled."""
vnic_template_info = self.ucsm_conf.get_vnic_templates_for_ucsm_ip(
vnic_template_info = self.ucsm_conf.get_vnic_template_for_ucsm_ip(
ucsm_ip)
vlan_name = self.make_vlan_name(vlan_id)
@ -867,8 +933,9 @@ class CiscoUcsmDriver(object):
vlan_id,
ucsm_ip)
self._delete_vlan_profile(handle, vlan_id, ucsm_ip)
for vlan_id in trunk_vlans:
self._delete_vlan_profile(handle, vlan_id, ucsm_ip)
if trunk_vlans:
for vlan_id in trunk_vlans:
self._delete_vlan_profile(handle, vlan_id, ucsm_ip)
def _handle_ucsm_exception(self, exception_type, profile_type,
profile_name, ucsm_ip):

View File

@ -92,6 +92,8 @@ vnic_template_dict = {
('2.2.2.2', 'physnet2'): ('org-root/org-Test-Sub', 'Test'),
}
PORT_PROFILE_1 = 'OS-PP-100'
class FakeNetworkContext(api.NetworkContext):
@ -163,6 +165,34 @@ class FakePortContext(object):
self._port['status'] = status
class FakeUcsmHandle(object):
"""Ucsm connection handle for testing purposes only."""
def __init__(self, port_profile):
self._port_profile = port_profile
self._times_called = 0
def StartTransaction(self):
return True
def GetManagedObject(self, class_path, class_id,
pp_dict):
self._times_called += 1
if self._times_called == 1:
return None
elif self._times_called == 2:
raise Exception("Port profile still in use by VMs.")
else:
return self._port_profile
def RemoveManagedObject(self, p_profile):
self._port_profile = None
def CompleteTransaction(self):
return
class TestCiscoUcsmMechDriver(testlib_api.SqlTestCase,
mocked.ConfigMixin):
@ -189,6 +219,7 @@ class TestCiscoUcsmMechDriver(testlib_api.SqlTestCase,
self.vif_type = const.VIF_TYPE_802_QBH
self.db = ucsm_db.UcsmDbModel()
self.ucsm_driver = ucsm_network_driver.CiscoUcsmDriver()
self.ucsm_driver.ucsm_db = ucsm_db.UcsmDbModel()
self.ucsm_config = conf.UcsmConfig()
def _create_network_context(self):
@ -838,3 +869,55 @@ class TestCiscoUcsmMechDriver(testlib_api.SqlTestCase,
host_id3 = 'compute3'
hostname = self.mech_driver._get_host_id(host_id3)
self.assertEqual(host_id3, hostname)
def test_port_profile_delete_table_add(self):
"""Verifies that add and get of 1 PP to delete table works."""
self.db.add_port_profile_to_delete_table('OS-PP-100', '10.10.10.10')
self.assertTrue(self.db.has_port_profile_to_delete('OS-PP-100',
'10.10.10.10'))
def test_pp_delete_table_add_multiple(self):
"""Verifies that add and get of multiple PPs to delete table works."""
self.db.add_port_profile_to_delete_table("OS-PP-100", "10.10.10.10")
self.db.add_port_profile_to_delete_table("OS-PP-200", "10.10.10.10")
all_pps = self.db.get_all_port_profiles_to_delete()
for pp in all_pps:
self.assertEqual("10.10.10.10", pp.device_id)
def test_remove_port_profile_from_table(self):
"""Verifies that removing entry from PP delete table works."""
self.db.add_port_profile_to_delete_table("OS-PP-100", "10.10.10.10")
self.db.remove_port_profile_to_delete("OS-PP-100", "10.10.10.10")
self.assertFalse(self.db.has_port_profile_to_delete("OS-PP-100",
"10.10.10.10"))
def test_remove_non_existent_port_profile_from_table(self):
self.assertIsNone(self.db.remove_port_profile_to_delete(
"OS-PP-100", "10.10.10.10"))
def test_port_profile_delete_on_ucsm(self):
"""Verifies that the PP delete retry logic."""
handle = FakeUcsmHandle(PORT_PROFILE_1)
self.ucsm_driver.ucsmsdk = mock.Mock()
self.ucsm_driver.ucsmsdk.VnicProfile.ClassId.return_value = "PP"
self.ucsm_driver.ucsmsdk.VnicProfile.NAME = PORT_PROFILE_1
self.ucsm_driver.ucsmsdk.VnicProfile.DN = "/org-root"
# 1st call to delete_port_profile is designed to not find
# the PP on the UCSM
self.ucsm_driver._delete_port_profile(
handle, PORT_PROFILE_1, UCSM_IP_ADDRESS_1)
# No entry added to the PP delete table
self.assertFalse(self.ucsm_driver.ucsm_db.has_port_profile_to_delete(
PORT_PROFILE_1, UCSM_IP_ADDRESS_1))
# 2nd call to delete_port_profile is designed to raise exception
self.ucsm_driver._delete_port_profile(
handle, PORT_PROFILE_1, UCSM_IP_ADDRESS_1)
# Failed delete results in entry being created in the PP delete table
self.assertTrue(self.ucsm_driver.ucsm_db.has_port_profile_to_delete(
PORT_PROFILE_1, UCSM_IP_ADDRESS_1))