Deprecate MySQL NDB Cluster Support

Traditionally, the MySQL support in oslo.db has assumed use of the
InnoDB storage engine. However, this isn't the only storage engine
available and a few years ago an effort was made to add support for
another storage engine, MySQL Cluster (NDB). The oslo.db aspects of this
effort were tracked via bug 1564110 [1] and from reading this bug and
looking at other patches related to this effort [2], it becomes obvious
that this was never seen through to the completion and the
OpenStack-wide effort never took off [3]. As a result, much of what is
here is in-effect dead code now.

Given no one is using this engine, there's no reason to keep it around.
Deprecate it with an eye on removing it sooner rather than later.

[1] https://bugs.launchpad.net/oslo.db/+bug/1564110
[2] https://review.opendev.org/q/owner:octave.orgeron%2540oracle.com
[3] https://review.opendev.org/c/openstack/openstack-specs/+/429940

Change-Id: Id5ddf1d6f47b8a572001f58ad8b9b8a7dbe4e8ac
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
Stephen Finucane 2022-07-14 11:33:19 +01:00
parent 8c9ef045d1
commit f32890e41d
9 changed files with 143 additions and 29 deletions

View File

@ -62,6 +62,12 @@ database_opts = [
cfg.BoolOpt( cfg.BoolOpt(
'mysql_enable_ndb', 'mysql_enable_ndb',
default=False, default=False,
deprecated_for_removal=True,
deprecated_since='12.1.0',
deprecated_reason=(
'Support for the MySQL NDB Cluster storage engine has been '
'deprecated and will be removed in a future release.'
),
help=( help=(
'If True, transparently enables support for handling ' 'If True, transparently enables support for handling '
'MySQL Cluster (NDB).' 'MySQL Cluster (NDB).'

View File

@ -316,6 +316,14 @@ class _TransactionFactory(object):
self._configure(False, kw) self._configure(False, kw)
def _configure(self, as_defaults, kw): def _configure(self, as_defaults, kw):
if 'mysql_enable_ndb' in kw:
debtcollector.deprecate(
(
'Support for the MySQL NDB Cluster storage engine has '
'been deprecated and will be removed in a future release.'
),
version='12.1.0',
)
if self._started: if self._started:
raise AlreadyStartedError( raise AlreadyStartedError(
@ -1239,6 +1247,7 @@ class LegacyEngineFacade(object):
:keyword mysql_enable_ndb: If True, transparently enables support for :keyword mysql_enable_ndb: If True, transparently enables support for
handling MySQL Cluster (NDB). handling MySQL Cluster (NDB).
(defaults to False) (defaults to False)
**DEPRECATED**
:keyword connection_recycle_time: Time period for connections to be :keyword connection_recycle_time: Time period for connections to be
recycled upon checkout (defaults to 3600) recycled upon checkout (defaults to 3600)
:keyword connection_debug: verbosity of SQL debugging information. :keyword connection_debug: verbosity of SQL debugging information.

View File

@ -23,6 +23,7 @@ import re
import time import time
from urllib import parse from urllib import parse
import debtcollector.removals
import debtcollector.renames import debtcollector.renames
import sqlalchemy import sqlalchemy
from sqlalchemy import event from sqlalchemy import event
@ -147,8 +148,19 @@ def _vet_url(url):
) )
@debtcollector.removals.removed_kwarg(
'mysql_enable_ndb',
message=(
'Support for the MySQL NDB Cluster storage engine has been deprecated '
'and will be removed in a future release.'
),
version='12.1.0',
)
@debtcollector.renames.renamed_kwarg( @debtcollector.renames.renamed_kwarg(
"idle_timeout", "connection_recycle_time", replace=True) 'idle_timeout',
'connection_recycle_time',
replace=True,
)
def create_engine(sql_connection, sqlite_fk=False, mysql_sql_mode=None, def create_engine(sql_connection, sqlite_fk=False, mysql_sql_mode=None,
mysql_enable_ndb=False, mysql_enable_ndb=False,
connection_recycle_time=3600, connection_recycle_time=3600,

View File

@ -15,18 +15,26 @@
import re import re
from oslo_db.sqlalchemy.types import String import debtcollector.removals
from sqlalchemy import event, schema from sqlalchemy import event, schema
from sqlalchemy.ext.compiler import compiles from sqlalchemy.ext.compiler import compiles
from sqlalchemy.types import String as _String from sqlalchemy.types import String as _String
from sqlalchemy.types import to_instance from sqlalchemy.types import to_instance
from oslo_db.sqlalchemy.types import String
engine_regex = re.compile("engine=innodb", re.IGNORECASE) engine_regex = re.compile("engine=innodb", re.IGNORECASE)
trans_regex = re.compile("savepoint|rollback|release savepoint", re.IGNORECASE) trans_regex = re.compile("savepoint|rollback|release savepoint", re.IGNORECASE)
@debtcollector.removals.remove(
message=(
'Support for the MySQL NDB Cluster storage engine has been deprecated '
'and will be removed in a future release.'
),
version='12.1.0',
)
def enable_ndb_support(engine): def enable_ndb_support(engine):
"""Enable NDB Support. """Enable NDB Support.
@ -36,16 +44,45 @@ def enable_ndb_support(engine):
engine.dialect._oslodb_enable_ndb_support = True engine.dialect._oslodb_enable_ndb_support = True
def _ndb_status(engine_or_compiler):
"""Test if NDB Support is enabled.
Function to test if NDB support is enabled or not.
.. note::
This is for internal use only while we deprecate and remove ndb
support. **Do not use this outside of oslo.db!**
"""
return getattr(
engine_or_compiler.dialect,
'_oslodb_enable_ndb_support',
False,
)
@debtcollector.removals.remove(
message=(
'Support for the MySQL NDB Cluster storage engine has been deprecated '
'and will be removed in a future release.'
),
version='12.1.0',
)
def ndb_status(engine_or_compiler): def ndb_status(engine_or_compiler):
"""Test if NDB Support is enabled. """Test if NDB Support is enabled.
Function to test if NDB support is enabled or not. Function to test if NDB support is enabled or not.
""" """
return getattr(engine_or_compiler.dialect, return _ndb_status(engine_or_compiler)
'_oslodb_enable_ndb_support',
False)
@debtcollector.removals.remove(
message=(
'Support for the MySQL NDB Cluster storage engine has been deprecated '
'and will be removed in a future release.'
),
version='12.1.0',
)
def init_ndb_events(engine): def init_ndb_events(engine):
"""Initialize NDB Events. """Initialize NDB Events.
@ -60,7 +97,7 @@ def init_ndb_events(engine):
convert InnoDB to NDBCLUSTER, drop SAVEPOINT requests, drop convert InnoDB to NDBCLUSTER, drop SAVEPOINT requests, drop
ROLLBACK requests, and drop RELEASE SAVEPOINT requests. ROLLBACK requests, and drop RELEASE SAVEPOINT requests.
""" """
if ndb_status(engine): if _ndb_status(engine):
statement = engine_regex.sub("ENGINE=NDBCLUSTER", statement) statement = engine_regex.sub("ENGINE=NDBCLUSTER", statement)
if re.match(trans_regex, statement): if re.match(trans_regex, statement):
statement = "SET @oslo_db_ndb_savepoint_rollback_disabled = 0;" statement = "SET @oslo_db_ndb_savepoint_rollback_disabled = 0;"
@ -68,6 +105,8 @@ def init_ndb_events(engine):
return statement, parameters return statement, parameters
# TODO(stephenfin): This is effectively deprecated and should be removed when
# we remove the rest of this module since it'll be a no-op then.
@compiles(schema.CreateTable, "mysql") @compiles(schema.CreateTable, "mysql")
def prefix_inserts(create_table, compiler, **kw): def prefix_inserts(create_table, compiler, **kw):
"""Replace InnoDB with NDBCLUSTER automatically. """Replace InnoDB with NDBCLUSTER automatically.
@ -76,12 +115,14 @@ def prefix_inserts(create_table, compiler, **kw):
convert InnoDB to NDBCLUSTER. Targets compiler events. convert InnoDB to NDBCLUSTER. Targets compiler events.
""" """
existing = compiler.visit_create_table(create_table, **kw) existing = compiler.visit_create_table(create_table, **kw)
if ndb_status(compiler): if _ndb_status(compiler):
existing = engine_regex.sub("ENGINE=NDBCLUSTER", existing) existing = engine_regex.sub("ENGINE=NDBCLUSTER", existing)
return existing return existing
# TODO(stephenfin): This is effectively deprecated and should be removed when
# we remove the rest of this module since it'll be a no-op then.
@compiles(String, "mysql") @compiles(String, "mysql")
def _compile_ndb_string(element, compiler, **kw): def _compile_ndb_string(element, compiler, **kw):
"""Process ndb specific overrides for String. """Process ndb specific overrides for String.
@ -95,7 +136,7 @@ def _compile_ndb_string(element, compiler, **kw):
mysql_ndb_type will change the column type to the requested mysql_ndb_type will change the column type to the requested
data type. data type.
""" """
if not ndb_status(compiler): if not _ndb_status(compiler):
return compiler.visit_string(element, **kw) return compiler.visit_string(element, **kw)
if element.mysql_ndb_length: if element.mysql_ndb_length:

View File

@ -12,6 +12,7 @@
import json import json
import debtcollector.removals
from sqlalchemy.dialects import mysql from sqlalchemy.dialects import mysql
from sqlalchemy.types import Integer, Text, TypeDecorator, String as _String from sqlalchemy.types import Integer, Text, TypeDecorator, String as _String
@ -113,6 +114,18 @@ class SoftDeleteInteger(TypeDecorator):
return int(value) return int(value)
# NOTE(stephenfin): We deprecate the whole class rather than just the
# ndb-related arguments, since without these arguments this is identical to the
# upstream SQLAlchemy type
@debtcollector.removals.removed_class(
'String',
message=(
'Support for the MySQL NDB Cluster storage engine has been '
'deprecated and will be removed in a future release. Use the '
'standard String type from sqlalchemy.types'
),
version='12.1.0',
)
class String(_String): class String(_String):
"""String subclass that implements oslo_db specific options. """String subclass that implements oslo_db specific options.
@ -124,7 +137,12 @@ class String(_String):
""" """
def __init__( def __init__(
self, length, mysql_ndb_length=None, mysql_ndb_type=None, **kw): self,
length,
mysql_ndb_length=None,
mysql_ndb_type=None,
**kw,
):
"""Initialize options.""" """Initialize options."""
super(String, self).__init__(length, **kw) super(String, self).__init__(length, **kw)
self.mysql_ndb_type = mysql_ndb_type self.mysql_ndb_type = mysql_ndb_type

View File

@ -1136,6 +1136,13 @@ def get_non_innodb_tables(connectable, skip_tables=('migrate_version',
return [i[0] for i in noninnodb] return [i[0] for i in noninnodb]
@debtcollector.removals.remove(
message=(
'Support for the MySQL NDB Cluster storage engine has been deprecated '
'and will be removed in a future release.'
),
version='12.1.0',
)
def get_non_ndbcluster_tables(connectable, skip_tables=None): def get_non_ndbcluster_tables(connectable, skip_tables=None):
"""Get a list of tables which don't use MySQL Cluster (NDB) storage engine. """Get a list of tables which don't use MySQL Cluster (NDB) storage engine.
@ -1187,7 +1194,8 @@ def get_foreign_key_constraint_name(engine, table_name, column_name):
@contextlib.contextmanager @contextlib.contextmanager
def suspend_fk_constraints_for_col_alter( def suspend_fk_constraints_for_col_alter(
engine, table_name, column_name, referents=[]): engine, table_name, column_name, referents=[],
):
"""Detect foreign key constraints, drop, and recreate. """Detect foreign key constraints, drop, and recreate.
This is used to guard against a column ALTER that on some backends This is used to guard against a column ALTER that on some backends
@ -1218,11 +1226,16 @@ def suspend_fk_constraints_for_col_alter(
:param referents: sequence of string table names to search for foreign :param referents: sequence of string table names to search for foreign
key constraints. A future version of this function may no longer key constraints. A future version of this function may no longer
require this argument, however for the moment it is required. require this argument, however for the moment it is required.
""" """
if ( debtcollector.deprecate(
not ndb.ndb_status(engine) (
): 'Support for the MySQL NDB Cluster storage engine has been '
'deprecated and will be removed in a future release.'
),
version='12.1.0',
)
if not ndb._ndb_status(engine):
yield yield
else: else:
with engine.connect() as conn: with engine.connect() as conn:

View File

@ -15,6 +15,7 @@
import logging import logging
from unittest import mock from unittest import mock
import warnings
from oslo_db import exception from oslo_db import exception
from oslo_db.sqlalchemy import enginefacade from oslo_db.sqlalchemy import enginefacade
@ -38,12 +39,16 @@ from sqlalchemy import Text
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
_MOCK_CONNECTION = 'mysql+pymysql://' _MOCK_CONNECTION = 'mysql+pymysql://'
_TEST_TABLE = Table("test_ndb", MetaData(), with warnings.catch_warnings(): # hide deprecation warning
Column('id', Integer, primary_key=True), _TEST_TABLE = Table(
Column('test1', String(255, mysql_ndb_type=TEXT)), "test_ndb",
Column('test2', String(4096, mysql_ndb_type=TEXT)), MetaData(),
Column('test3', String(255, mysql_ndb_length=64)), Column('id', Integer, primary_key=True),
mysql_engine='InnoDB') Column('test1', String(255, mysql_ndb_type=TEXT)),
Column('test2', String(4096, mysql_ndb_type=TEXT)),
Column('test3', String(255, mysql_ndb_length=64)),
mysql_engine='InnoDB',
)
class NDBMockTestBase(test_base.BaseTestCase): class NDBMockTestBase(test_base.BaseTestCase):
@ -57,7 +62,9 @@ class NDBMockTestBase(test_base.BaseTestCase):
self.addCleanup( self.addCleanup(
setattr, test_engine.dialect, "_oslodb_enable_ndb_support", False setattr, test_engine.dialect, "_oslodb_enable_ndb_support", False
) )
ndb.init_ndb_events(test_engine) # hide deprecation warnings
with warnings.catch_warnings():
ndb.init_ndb_events(test_engine)
class NDBEventTestCase(NDBMockTestBase): class NDBEventTestCase(NDBMockTestBase):
@ -164,9 +171,10 @@ class NDBOpportunisticTestCase(
# if we want NDB, make a new local engine that uses the # if we want NDB, make a new local engine that uses the
# URL / database / schema etc. of the provisioned engine, # URL / database / schema etc. of the provisioned engine,
# since NDB-ness is a per-table thing # since NDB-ness is a per-table thing
self.engine = engines.create_engine( with warnings.catch_warnings(): # hide deprecation warnings
self.engine.url, mysql_enable_ndb=True self.engine = engines.create_engine(
) self.engine.url, mysql_enable_ndb=True
)
self.addCleanup(self.engine.dispose) self.addCleanup(self.engine.dispose)
self.test_table = _TEST_TABLE self.test_table = _TEST_TABLE
try: try:
@ -176,7 +184,8 @@ class NDBOpportunisticTestCase(
def test_ndb_enabled(self): def test_ndb_enabled(self):
self.init_db(True) self.init_db(True)
self.assertTrue(ndb.ndb_status(self.engine)) with warnings.catch_warnings(): # hide deprecation warnings
self.assertTrue(ndb.ndb_status(self.engine))
self.assertIsInstance(self.test_table.c.test1.type, TINYTEXT) self.assertIsInstance(self.test_table.c.test1.type, TINYTEXT)
self.assertIsInstance(self.test_table.c.test2.type, Text) self.assertIsInstance(self.test_table.c.test2.type, Text)
self.assertIsInstance(self.test_table.c.test3.type, String) self.assertIsInstance(self.test_table.c.test3.type, String)
@ -185,7 +194,8 @@ class NDBOpportunisticTestCase(
def test_ndb_disabled(self): def test_ndb_disabled(self):
self.init_db(False) self.init_db(False)
self.assertFalse(ndb.ndb_status(self.engine)) with warnings.catch_warnings(): # hide deprecation warnings
self.assertFalse(ndb.ndb_status(self.engine))
self.assertIsInstance(self.test_table.c.test1.type, String) self.assertIsInstance(self.test_table.c.test1.type, String)
self.assertEqual(255, self.test_table.c.test1.type.length) self.assertEqual(255, self.test_table.c.test1.type.length)
self.assertIsInstance(self.test_table.c.test2.type, String) self.assertIsInstance(self.test_table.c.test2.type, String)

View File

@ -1020,7 +1020,7 @@ class TestMigrationUtils(db_test_base._DbTestCase):
normalize_fk_entries(existing_foreign_keys) normalize_fk_entries(existing_foreign_keys)
) )
with mock.patch("oslo_db.sqlalchemy.ndb.ndb_status", with mock.patch("oslo_db.sqlalchemy.ndb._ndb_status",
mock.Mock(return_value=True)): mock.Mock(return_value=True)):
with utils.suspend_fk_constraints_for_col_alter( with utils.suspend_fk_constraints_for_col_alter(
self.engine, 'a', 'id', referents=['b', 'c']): self.engine, 'a', 'id', referents=['b', 'c']):
@ -1034,7 +1034,7 @@ class TestMigrationUtils(db_test_base._DbTestCase):
self.assertEqual(existing_foreign_keys, get_fk_entries()) self.assertEqual(existing_foreign_keys, get_fk_entries())
with mock.patch("oslo_db.sqlalchemy.ndb.ndb_status", with mock.patch("oslo_db.sqlalchemy.ndb._ndb_status",
mock.Mock(return_value=True)): mock.Mock(return_value=True)):
with utils.suspend_fk_constraints_for_col_alter( with utils.suspend_fk_constraints_for_col_alter(
self.engine, 'b', 'archive_id', referents=['c']): self.engine, 'b', 'archive_id', referents=['c']):

View File

@ -0,0 +1,5 @@
---
deprecations:
- |
MySQL NDB Cluster support has been deprecated for removal. It appears no
one is using this functionality and it's poorly understood.