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:
parent
9d13d885de
commit
1a0e03c3dc
|
@ -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
|
||||
|
|
|
@ -1 +1 @@
|
|||
b29f1026b281
|
||||
203b495958cf
|
||||
|
|
|
@ -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')
|
||||
)
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in New Issue