Add Datastore Version Registry Extension

Current, users can config default datastore registry for managers not by
verions. This make you can flexible when have some experimental
datastore.

With this patch, users with the administrator role can configure the
datastore registry external for each datastore version using a command,
without editing configuration files.

Story: #2010860
Task: #48534
Change-Id: I910a1ba4a9216ab29faeed03198113b4acb2cb81
This commit is contained in:
Bo Tran Van 2023-08-07 10:51:00 +07:00 committed by Bo Tran
parent 83a525067d
commit b127068c20
19 changed files with 276 additions and 65 deletions

View File

@ -202,6 +202,8 @@ trove-manage datastore_version_update
datastore version_name manager
image_id packages active
--image-tags <image_tags>
--registry-ext <registry_ext>
--repl-strategy <repl_strategy>
Add or update a datastore version. If the datastore version already exists,
all values except the datastore name and version will be updated.
@ -240,6 +242,14 @@ all values except the datastore name and version will be updated.
than ID especially when new guest image is uploaded to Glance, Trove can pick
up the latest image automatically for creating instances.
``--registry-ext``
Extension for default datastore version managers. Allows the use of custom managers
for each of the datastore versions supported by Trove.
``--repl-strategy``
Extension for default strategy for replication. Allows the use of custom
replication strategy for each of the datastores supported by Trove.
trove-manage db_downgrade
~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,5 @@
---
features:
- Users with the administrator role can configure the datastore registry external
for each datastore version using a command, without editing configuration files.

View File

@ -28,14 +28,21 @@ from trove.guestagent import volume
CONF = cfg.CONF
# The guest_id opt definition must match the one in common/cfg.py
CONF.register_opts([openstack_cfg.StrOpt('guest_id', default=None,
help="ID of the Guest Instance."),
openstack_cfg.StrOpt('instance_rpc_encr_key',
help=('Key (OpenSSL aes_cbc) for '
'instance RPC encryption.')),
openstack_cfg.BoolOpt('network_isolation',
help='whether to plug user defined '
'port to database container')])
CONF.register_opts([
openstack_cfg.StrOpt('guest_id', default=None,
help="ID of the Guest Instance."),
openstack_cfg.StrOpt('instance_rpc_encr_key',
help=('Key (OpenSSL aes_cbc) for '
'instance RPC encryption.')),
openstack_cfg.BoolOpt('network_isolation',
help='whether to plug user defined '
'port to database container'),
openstack_cfg.StrOpt('replication_strategy',
default='trove.guestagent.strategies.replication.'
'mysql_gtid.MysqlGTIDReplication',
help='Namespace to load replication strategies from.')
])
LOG = logging.getLogger(__name__)
@ -61,7 +68,7 @@ def main():
if not CONF.guest_id:
msg = (_("The guest_id parameter is not set. guest_info.conf "
"was not injected into the guest or not read by guestagent"))
"was not injected into the guest or not read by guestagent"))
raise RuntimeError(msg)
if CONF.network_isolation:
# disable user-defined port to avoid potential default gateway

View File

@ -62,16 +62,19 @@ class Commands(object):
print(e)
def datastore_version_update(self, datastore, version_name, manager,
image_id, packages, active, image_tags=None,
version=None):
image_id, packages, active, registry_ext,
repl_strategy,
image_tags=None, version=None):
try:
datastore_models.update_datastore_version(datastore,
version_name,
manager,
image_id,
image_tags,
packages, active,
version=version)
datastore_models.update_datastore_version(
datastore,
version_name,
manager,
image_id,
image_tags,
packages, active,
registry_ext, repl_strategy,
version=version)
print("Datastore version '%s(%s)' updated." %
(version_name, version or version_name))
except exception.DatastoreNotFound as e:
@ -251,6 +254,16 @@ def main():
'image_id',
help='ID of the image used to create an instance of '
'the datastore version.')
parser.add_argument(
'--registry-ext',
help='Extension for default datastore managers. '
'Allows the use of custom managers for each of '
'the datastores supported by Trove.')
parser.add_argument(
'--repl-strategy',
help='Extension for default strategy for replication. '
'Allows the use of custom managers for each of '
'the datastores supported by Trove.')
parser.add_argument(
'packages', help='Packages required by the datastore version that '
'are installed on the guest image.')

View File

@ -993,6 +993,8 @@ mgmt_datastore_version = {
"packages": package_list,
"image": uuid,
"image_tags": image_tags,
"registry_ext": non_empty_string,
"repl_strategy": non_empty_string,
"active": {"enum": [True, False]},
"default": {"enum": [True, False]},
"version": non_empty_string
@ -1010,6 +1012,8 @@ mgmt_datastore_version = {
"packages": package_list,
"image": uuid,
"image_tags": image_tags,
"registry_ext": non_empty_string,
"repl_strategy": non_empty_string,
"active": {"enum": [True, False]},
"default": {"enum": [True, False]},
"name": non_empty_string

View File

@ -534,7 +534,7 @@ common_opts = [
cfg.BoolOpt(
'enable_volume_az', default=False,
help='If true create the volume in the same availability-zone as the '
'instance'),
'instance')
]
# Mysql

View File

@ -20,3 +20,30 @@ DOCKER_HOST_NIC_MODE = "docker-hostnic"
DOCKER_BRIDGE_MODE = "bridge"
MYSQL_HOST_SOCKET_PATH = "/var/lib/mysqld"
POSTGRESQL_HOST_SOCKET_PATH = "/var/lib/postgresql-socket"
REGISTRY_EXT_DEFAULTS = {
'mysql':
'trove.guestagent.datastore.mysql.manager.Manager',
'mariadb':
'trove.guestagent.datastore.mariadb.manager.Manager',
'postgresql':
'trove.guestagent.datastore.postgres.manager.PostgresManager',
'percona':
'trove.guestagent.datastore.experimental.percona.manager.Manager',
'pxc':
'trove.guestagent.datastore.experimental.pxc.manager.Manager',
'redis':
'trove.guestagent.datastore.experimental.redis.manager.Manager',
'cassandra':
'trove.guestagent.datastore.experimental.cassandra.manager.Manager',
'couchbase':
'trove.guestagent.datastore.experimental.couchbase.manager.Manager',
'mongodb':
'trove.guestagent.datastore.experimental.mongodb.manager.Manager',
'couchdb':
'trove.guestagent.datastore.experimental.couchdb.manager.Manager',
'vertica':
'trove.guestagent.datastore.experimental.vertica.manager.Manager',
'db2':
'trove.guestagent.datastore.experimental.db2.manager.Manager',
}

View File

@ -20,6 +20,7 @@ from oslo_utils import uuidutils
from trove.common import cfg
from trove.common.clients import create_nova_client
from trove.common import constants
from trove.common import exception
from trove.common.i18n import _
from trove.common import timeutils
@ -212,6 +213,7 @@ class CapabilityOverride(BaseCapability):
specific datastore version that overrides the default setting in the
base capability's entry for Trove.
"""
def __init__(self, db_info):
super(CapabilityOverride, self).__init__(db_info)
# This *may* be better solved with a join in the SQLAlchemy model but
@ -467,6 +469,14 @@ class DatastoreVersion(object):
def packages(self):
return self.db_info.packages
@property
def registry_ext(self):
return self.db_info.registry_ext
@property
def repl_strategy(self):
return self.db_info.repl_strategy
@property
def active(self):
return (True if self.db_info.active else False)
@ -600,7 +610,8 @@ def update_datastore(name, default_version):
def update_datastore_version(datastore, name, manager, image_id, image_tags,
packages, active, version=None, new_name=None):
packages, active, registry_ext=None,
repl_strategy=None, version=None, new_name=None):
"""Create or update datastore version."""
version = version or name
db_api.configure_db(CONF)
@ -622,6 +633,16 @@ def update_datastore_version(datastore, name, manager, image_id, image_tags,
if type(image_tags) is list else image_tags)
ds_version.packages = packages
ds_version.active = active
if not registry_ext:
registry_ext = constants.REGISTRY_EXT_DEFAULTS.get(manager)
ds_version.registry_ext = registry_ext
if not repl_strategy:
repl_strategy = "%(repl_namespace)s.%(repl_strategy)s" % {
'repl_namespace': CONF.get(manager).replication_namespace,
'repl_strategy': CONF.get(manager).replication_strategy
}
ds_version.repl_strategy = repl_strategy
db_api.save(ds_version)

View File

@ -84,14 +84,17 @@ class DatastoreVersionView(object):
"links": self._build_links(),
}
if include_datastore_id:
datastore_version_dict["datastore"] = (self.datastore_version.
datastore_id)
datastore_version_dict["datastore"] = (
self.datastore_version.datastore_id)
if self.context.is_admin:
datastore_version_dict['active'] = self.datastore_version.active
datastore_version_dict['packages'] = (self.datastore_version.
packages)
datastore_version_dict['packages'] = (
self.datastore_version.packages)
datastore_version_dict['image'] = self.datastore_version.image_id
datastore_version_dict[
'registry_ext'] = self.datastore_version.registry_ext
datastore_version_dict[
'repl_strategy'] = self.datastore_version.repl_strategy
image_tags = []
if self.datastore_version.image_tags:
image_tags = self.datastore_version.image_tags.split(',')

View File

@ -0,0 +1,79 @@
# Copyright 2023 Bizfly Cloud
# All Rights Reserved.
#
# 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 sqlalchemy.schema import Column
from sqlalchemy.schema import MetaData
from sqlalchemy.sql.expression import select
from sqlalchemy.sql.expression import update
from sqlalchemy import text
from trove.common.constants import REGISTRY_EXT_DEFAULTS
from trove.db.sqlalchemy.migrate_repo.schema import Table
from trove.db.sqlalchemy.migrate_repo.schema import Text
repl_namespaces = {
"mariadb": "trove.guestagent.strategies.replication.mariadb_gtid",
"mongodb":
"trove.guestagent.strategies.replication.experimental.mongo_impl",
"mysql": "trove.guestagent.strategies.replication.mysql_gtid",
"percona": "trove.guestagent.strategies.replication.mysql_gtid",
"postgresql": "trove.guestagent.strategies.replication.postgresql",
"pxc": "trove.guestagent.strategies.replication.mysql_gtid",
"redis": "trove.guestagent.strategies.replication.experimental.redis_sync",
}
repl_strategies = {
"mariadb": "MariaDBGTIDReplication",
"mongodb": "Replication",
"mysql": "MysqlGTIDReplication",
"percona": "MysqlGTIDReplication",
"postgresql": "PostgresqlReplicationStreaming",
"pxc": "MysqlGTIDReplication",
"redis": "RedisSyncReplication",
}
def upgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
ds_version = Table('datastore_versions', meta, autoload=True)
ds_version.create_column(Column('registry_ext', Text(), nullable=True))
ds_version.create_column(Column('repl_strategy', Text(), nullable=True))
ds_versions = select(
columns=[text("id"), text("manager")],
from_obj=ds_version
).execute()
# Use 'name' value as init 'version' value
for version in ds_versions:
registry_ext = REGISTRY_EXT_DEFAULTS.get(version.manager, '')
repl_strategy = "%(repl_namespace)s.%(repl_strategy)s" % {
'repl_namespace': repl_namespaces.get(version.manager, ''),
'repl_strategy': repl_strategies.get(version.manager, '')
}
update(
table=ds_version,
whereclause=text("id='%s'" % version.id),
values=dict(
registry_ext=registry_ext,
repl_strategy=repl_strategy)
).execute()
ds_version.c.registry_ext.alter(nullable=False)
ds_version.c.repl_strategy.alter(nullable=False)

View File

@ -52,6 +52,8 @@ class DatastoreVersionController(wsgi.Controller):
# For backward compatibility, use name as default value for version if
# not specified
version_str = body['version'].get('version', version_name)
registry_ext = body['version'].get('registry_ext')
repl_strategy = body['version'].get('repl_strategy')
LOG.info("Tenant: '%(tenant)s' is adding the datastore "
"version: '%(version)s' to datastore: '%(datastore)s'",
@ -80,10 +82,11 @@ class DatastoreVersionController(wsgi.Controller):
raise exception.DatastoreVersionAlreadyExists(
name=version_name, version=version_str)
except exception.DatastoreVersionNotFound:
models.update_datastore_version(datastore.name, version_name,
manager, image_id, image_tags,
packages, active,
version=version_str)
models.update_datastore_version(
datastore.name, version_name,
manager, image_id, image_tags,
packages, active, registry_ext, repl_strategy,
version=version_str)
if default:
models.update_datastore(datastore.name, version_name)
@ -129,6 +132,9 @@ class DatastoreVersionController(wsgi.Controller):
if type(packages) is list:
packages = ','.join(packages)
registry_ext = body.get('registry_ext', datastore_version.registry_ext)
repl_strategy = body.get(
'repl_strategy', datastore_version.repl_strategy)
if image_id or image_tags:
client = clients.create_glance_client(context)
common_glance.get_image_id(client, image_id, image_tags)
@ -147,12 +153,12 @@ class DatastoreVersionController(wsgi.Controller):
if not image_id and not image_tags:
raise exception.BadRequest("Image must be specified.")
models.update_datastore_version(datastore_version.datastore_name,
datastore_version.name,
manager, image_id, image_tags,
packages, active,
version=datastore_version.version,
new_name=name)
models.update_datastore_version(
datastore_version.datastore_name,
datastore_version.name,
manager, image_id, image_tags,
packages, active, registry_ext, repl_strategy,
version=datastore_version.version, new_name=name)
if default:
models.update_datastore(datastore_version.datastore_name,

View File

@ -32,6 +32,8 @@ class DatastoreVersionView(object):
"packages": (self.datastore_version.packages.split(
',') if self.datastore_version.packages else ['']),
"active": self.datastore_version.active,
"registry_ext": self.datastore_version.registry_ext,
"repl_strategy": self.datastore_version.repl_strategy,
"default": self.datastore_version.default}
return {'version': datastore_version_dict}

View File

@ -23,42 +23,18 @@ handles RPC calls relating to Platform specific operations.
"""
from itertools import chain
import os
from itertools import chain
from oslo_log import log as logging
from trove.common import cfg
from trove.common import constants
from trove.common.i18n import _
from trove.common import utils
LOG = logging.getLogger(__name__)
defaults = {
'mysql':
'trove.guestagent.datastore.mysql.manager.Manager',
'mariadb':
'trove.guestagent.datastore.mariadb.manager.Manager',
'postgresql':
'trove.guestagent.datastore.postgres.manager.PostgresManager',
'percona':
'trove.guestagent.datastore.experimental.percona.manager.Manager',
'pxc':
'trove.guestagent.datastore.experimental.pxc.manager.Manager',
'redis':
'trove.guestagent.datastore.experimental.redis.manager.Manager',
'cassandra':
'trove.guestagent.datastore.experimental.cassandra.manager.Manager',
'couchbase':
'trove.guestagent.datastore.experimental.couchbase.manager.Manager',
'mongodb':
'trove.guestagent.datastore.experimental.mongodb.manager.Manager',
'couchdb':
'trove.guestagent.datastore.experimental.couchdb.manager.Manager',
'vertica':
'trove.guestagent.datastore.experimental.vertica.manager.Manager',
'db2':
'trove.guestagent.datastore.experimental.db2.manager.Manager',
}
CONF = cfg.CONF
@ -67,8 +43,8 @@ def get_custom_managers():
def datastore_registry():
return dict(chain(defaults.items(),
get_custom_managers().items()))
return dict(chain(constants.REGISTRY_EXT_DEFAULTS.items(),
get_custom_managers().items()))
def get_filesystem_volume_stats(fs_path):

View File

@ -37,6 +37,12 @@ def get_instance(manager):
if not __replication_instance or manager != __replication_manager:
replication_strategy = get_strategy(manager)
__replication_namespace = CONF.get(manager).replication_namespace
if CONF.replication_strategy:
replication_strategy = CONF.replication_strategy.split('.')[-1]
__replication_namespace = '.'.join(
CONF.replication_strategy.split('.')[0:-1])
replication_strategy_cls = get_strategy_cls(
replication_strategy, __replication_namespace)
__replication_instance = replication_strategy_cls()

View File

@ -33,6 +33,7 @@ from sqlalchemy import func
from trove.backup.models import Backup
from trove.common import cfg
from trove.common import clients
from trove.common import constants
from trove.common import crypto_utils as cu
from trove.common import exception
from trove.common.i18n import _
@ -992,6 +993,28 @@ class BaseInstance(SimpleInstance):
return userdata if userdata else ""
@property
def datastore_registry_ext(self):
registry_ext = constants.REGISTRY_EXT_DEFAULTS.get(
self.ds_version.manager)
if self.ds_version.registry_ext:
registry_ext = self.ds_version.registry_ext
return "%(manager)s:%(registry_ext)s" % {
"manager": self.ds_version.manager,
"registry_ext": registry_ext
}
@property
def datastore_repl_strategy(self):
if self.ds_version.repl_strategy:
return self.ds_version.repl_strategy
return "%s.%s" % (
CONF.get(self.ds_version.manager).replication_namespace,
CONF.get(self.ds_version.manager).replication_strategy
)
def get_injected_files(self,
datastore_manager,
datastore_version,
@ -1014,8 +1037,11 @@ class BaseInstance(SimpleInstance):
"datastore_manager=%s\n"
"datastore_version=%s\n"
"tenant_id=%s\n"
"datastore_registry_ext=%s\n"
"replication_strategy=%s\n"
% (self.id, datastore_manager, datastore_version,
self.tenant_id)
self.tenant_id, self.datastore_registry_ext,
self.datastore_repl_strategy)
)
}

View File

@ -67,6 +67,8 @@ class TestDatastoreVersionController(trove_testtools.TestCase):
"datastore_manager": "mysql",
"image": image_id,
"image_tags": [],
"registry_ext": "registry_ext",
"repl_strategy": "repl_strategy",
"active": True,
"default": False
}
@ -85,6 +87,8 @@ class TestDatastoreVersionController(trove_testtools.TestCase):
"name": ver_name,
"datastore_manager": "mysql",
"image_tags": ['a', 'b', 'c', 'd', 'e', 'f'],
"registry_ext": "registry_ext",
"repl_strategy": "repl_strategy",
"active": True,
"default": False
}
@ -104,6 +108,8 @@ class TestDatastoreVersionController(trove_testtools.TestCase):
"datastore_manager": "mysql",
"image": image_id,
"image_tags": [],
"registry_ext": "registry_ext",
"repl_strategy": "repl_strategy",
"active": True,
"default": False
}
@ -129,6 +135,8 @@ class TestDatastoreVersionController(trove_testtools.TestCase):
"datastore_manager": "mysql",
"image": image_id,
"image_tags": [],
"registry_ext": "registry_ext",
"repl_strategy": "repl_strategy",
"packages": "test-pkg",
"active": True,
"default": True
@ -152,6 +160,8 @@ class TestDatastoreVersionController(trove_testtools.TestCase):
"datastore_manager": "mysql",
"image": image_id,
"image_tags": [],
"registry_ext": "registry_ext",
"repl_strategy": "repl_strategy",
"packages": "",
"active": True,
"default": False,
@ -177,6 +187,8 @@ class TestDatastoreVersionController(trove_testtools.TestCase):
"name": ver_name,
"datastore_manager": "mysql",
"image_tags": ["trove", "mysql"],
"registry_ext": "registry_ext",
"repl_strategy": "repl_strategy",
"active": True,
"default": True
}
@ -209,6 +221,8 @@ class TestDatastoreVersionController(trove_testtools.TestCase):
"datastore_manager": "mysql",
"image": image_id,
"image_tags": [],
"registry_ext": "registry_ext",
"repl_strategy": "repl_strategy",
"packages": "test-pkg",
"active": True,
"default": True

View File

@ -63,6 +63,8 @@ class MockMgmtInstanceTest(trove_testtools.TestCase):
name='5.5' + str(uuid.uuid4()),
manager='mysql',
image_id=str(uuid.uuid4()),
registry_ext="registry_ext",
repl_strategy="repl_strategy",
active=1,
packages="mysql-server-5.5"
)

View File

@ -136,6 +136,8 @@ class CreateInstanceTest(trove_testtools.TestCase):
manager="mysql",
image_id="image_id",
packages="",
registry_ext="registry_ext",
repl_strategy="repl_strategy",
active=True))
self.volume_size = 1
self.az = "az"
@ -264,6 +266,8 @@ class TestInstanceUpgrade(trove_testtools.TestCase):
packages=str(uuid.uuid4()),
datastore_id=self.datastore.id,
manager='test',
registry_ext="registry_ext",
repl_strategy="repl_strategy",
active=1)
self.datastore_version2 = datastore_models.DBDatastoreVersion.create(
@ -273,6 +277,8 @@ class TestInstanceUpgrade(trove_testtools.TestCase):
packages=str(uuid.uuid4()),
datastore_id=self.datastore.id,
manager='test',
registry_ext="registry_ext",
repl_strategy="repl_strategy",
active=1)
self.safe_nova_client = clients.create_nova_client
@ -337,6 +343,8 @@ class TestReplication(trove_testtools.TestCase):
packages=str(uuid.uuid4()),
datastore_id=self.datastore.id,
manager='mysql',
registry_ext="registry_ext",
repl_strategy="repl_strategy",
active=1)
self.databases = []

View File

@ -59,6 +59,8 @@ class BaseInstanceStatusTestCase(trove_testtools.TestCase):
name='5.7' + str(uuid.uuid4()),
manager='mysql',
image_id=str(uuid.uuid4()),
registry_ext="registry_ext",
repl_strategy="repl_strategy",
active=1,
packages="mysql-server-5.7"
)