Add support for mariadb 11.4 & 11.8

This commits adds support for Mariadb-11.4 & 11.8

Change-Id: I16166475d665defe046dee9c79646af4035ad841
Signed-off-by: wu.chunyang <wchy1001@gmail.com>
This commit is contained in:
wu.chunyang
2025-06-10 10:43:28 +08:00
parent e92f23471a
commit 9e46762ae2
13 changed files with 348 additions and 135 deletions

View File

@@ -527,18 +527,18 @@ function create_registry_container {
container=$(sudo docker ps -a --format "{{.Names}}" --filter name=registry)
if [ -z $container ]; then
sudo docker run -d --net=host -e REGISTRY_HTTP_ADDR=0.0.0.0:4000 --restart=always -v /opt/trove_registry/:/var/lib/registry --name registry quay.io/openstack.trove/registry:2
for img in {"mysql:8.0","mariadb:10.4","postgres:12"};
for img in {"mysql:8.0","mariadb:11.4","postgres:12"};
do
sudo docker pull quay.io/openstack.trove/${img} && sudo docker tag quay.io/openstack.trove/${img} 127.0.0.1:4000/trove-datastores/${img} && sudo docker push 127.0.0.1:4000/trove-datastores/${img}
done
pushd $DEST/trove/backup
# build backup images
sudo docker build --network host -t 127.0.0.1:4000/trove-datastores/db-backup-mysql:8.0 --build-arg DATASTORE=mysql --build-arg DATASTORE_VERSION=8.0 .
sudo docker build --network host -t 127.0.0.1:4000/trove-datastores/db-backup-mariadb:10.4 --build-arg DATASTORE=mariadb --build-arg BASE_OS_VERSION=20.04 --build-arg DATASTORE_VERSION=10.4 .
sudo docker build --network host -t 127.0.0.1:4000/trove-datastores/db-backup-mariadb:11.4 --build-arg DATASTORE=mariadb --build-arg DATASTORE_VERSION=11.4 .
sudo docker build --network host -t 127.0.0.1:4000/trove-datastores/db-backup-postgresql:12 --build-arg DATASTORE=postgresql --build-arg BASE_OS_VERSION=20.04 --build-arg DATASTORE_VERSION=12 .
popd
# push backup images
for backupimg in {"db-backup-mysql:8.0","db-backup-mariadb:10.4","db-backup-postgresql:12"};
for backupimg in {"db-backup-mysql:8.0","db-backup-mariadb:11.4","db-backup-postgresql:12"};
do
sudo docker push 127.0.0.1:4000/trove-datastores/${backupimg}
done

View File

@@ -0,0 +1,4 @@
---
features:
- |
Add support of mariadb11.4 & 11.8 and remove the support of mariadb10.4

View File

@@ -13,15 +13,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo_log import log as logging
from oslo_utils.excutils import save_and_reraise_exception
from trove.common import cfg
from trove.common import exception
from trove.common import utils
from trove.guestagent.common import operating_system
from trove.guestagent.utils import docker as docker_utils
from trove.guestagent.datastore.mariadb import service
from trove.guestagent.datastore.mysql_common import manager
from trove.guestagent.datastore.mysql_common import service as mysql_service
CONF = cfg.CONF
@@ -30,7 +32,7 @@ LOG = logging.getLogger(__name__)
class Manager(manager.MySqlManager):
def __init__(self):
status = mysql_service.BaseMySqlAppStatus(self.docker_client)
status = service.MariadbAppStatus(self.docker_client)
app = service.MariaDBApp(status, self.docker_client)
adm = service.MariaDBAdmin(app)
@@ -77,3 +79,49 @@ class Manager(manager.MySqlManager):
LOG.error("Run pre_create_backup failed, error: %s" % str(e))
raise exception.BackupCreationError(str(e))
return status
def reset_password_for_restore(self, ds_version=None,
data_dir='/var/lib/mysql/data'):
"""Reset the root password after restore the db data.
uses --skip-grant-tables to temporarily disable auth and
directly reset the root password via SQL.
"""
LOG.info('Starting to reset password for restore')
try:
root_pass = self.app.get_auth_password(file="root.cnf")
except exception.UnprocessableEntity:
root_pass = utils.generate_random_password()
self.app.save_password('root', root_pass)
command = (
f'--skip-grant-tables '
f'--datadir={data_dir} '
)
reset_sql = (
"FLUSH PRIVILEGES; "
"ALTER USER 'root'@'localhost' IDENTIFIED BY '{}';"
).format(root_pass)
reset_command = ["mariadb", "-u", "root", "-e", reset_sql]
# Start the database container process.
try:
self.app.start_db(ds_version=ds_version, command=command)
docker_utils.run_command(self.app.docker_client, reset_command)
except Exception as err:
with save_and_reraise_exception():
LOG.error('Failed to reset password for restore, error: %s',
str(err))
finally:
try:
LOG.debug(
'The init container log: %s',
docker_utils.get_container_logs(self.app.docker_client)
)
docker_utils.remove_container(self.app.docker_client)
except Exception as err:
LOG.error('Failed to remove container. error: %s',
str(err))
pass
LOG.info('Finished to reset password for restore')

View File

@@ -24,13 +24,81 @@ from trove.common import utils
from trove.guestagent.datastore.mysql_common import service as mysql_service
from trove.guestagent.utils import docker as docker_util
from trove.guestagent.utils import mysql as mysql_util
from trove.instance import service_status
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
class MariadbAppStatus(mysql_service.BaseMySqlAppStatus):
def _get_container_status(self):
status = docker_util.get_container_status(self.docker_client)
if status == "running":
root_pass = mysql_util.BaseDbApp.get_auth_password(file="root.cnf")
cmd = 'mysql -uroot -p%s -e "select 1;"' % root_pass
try:
docker_util.run_command(self.docker_client, cmd)
return service_status.ServiceStatuses.HEALTHY
except Exception as exc:
LOG.warning('Failed to run docker command, error: %s',
str(exc))
container_log = docker_util.get_container_logs(
self.docker_client, tail='all')
LOG.debug('container log: \n%s', '\n'.join(container_log))
return service_status.ServiceStatuses.RUNNING
elif status == "not running":
return service_status.ServiceStatuses.SHUTDOWN
elif status == "restarting":
return service_status.ServiceStatuses.SHUTDOWN
elif status == "paused":
return service_status.ServiceStatuses.PAUSED
elif status == "exited":
return service_status.ServiceStatuses.SHUTDOWN
elif status == "dead":
return service_status.ServiceStatuses.CRASHED
else:
return service_status.ServiceStatuses.UNKNOWN
def get_actual_db_status(self):
"""Check database service status."""
health = docker_util.get_container_health(self.docker_client)
LOG.debug('container health status: %s', health)
if health == "healthy":
return service_status.ServiceStatuses.HEALTHY
elif health == "starting":
return service_status.ServiceStatuses.RUNNING
elif health == "unhealthy":
# In case the container was stopped
status = docker_util.get_container_status(self.docker_client)
if status == "exited":
return service_status.ServiceStatuses.SHUTDOWN
else:
return service_status.ServiceStatuses.CRASHED
# if the health status is one of unkown or None, let's check
# container status .this is for the compatibility with the
# old datastores.
return self._get_container_status()
class MariaDBApp(mysql_service.BaseMySqlApp):
HEALTHCHECK = {
"test": ["CMD", "healthcheck.sh", "--defaults-file",
"/var/lib/mysql/data/.my-healthcheck.cnf",
"--connect", "--innodb_initialized"],
"start_period": 10 * 1000000000, # 10 seconds in nanoseconds
"interval": 10 * 1000000000,
"timeout": 5 * 1000000000,
"retries": 3
}
# # to regenerate the .my-healthcheck.cnf after restoring
_extra_envs = {"MARIADB_AUTO_UPGRADE": 1}
# Set to True for io_uring feature
_previledged = True
def __init__(self, status, docker_client):
super(MariaDBApp, self).__init__(status, docker_client)
@@ -86,9 +154,13 @@ class MariaDBApp(mysql_service.BaseMySqlApp):
with mysql_util.SqlClient(self.get_engine()) as client:
client.execute(cmd)
def wipe_ib_logfiles(self):
# mariadb_backup doesn't need to delete this file
pass
def reset_data_for_restore_snapshot(self, data_dir):
"""This function try remove slave status in database"""
command = "mysqld --skip-slave-start=ON --datadir=%s" % data_dir
command = "--skip-slave-start=ON --datadir=%s" % data_dir
extra_volumes = {
"/etc/mysql": {"bind": "/etc/mysql", "mode": "rw"},

View File

@@ -438,6 +438,8 @@ class BaseMySqlAdmin(object, metaclass=abc.ABCMeta):
class BaseMySqlApp(service.BaseDbApp):
_configuration_manager = None
_extra_envs = {}
_previledged = False
@property
def configuration_manager(self):
@@ -623,6 +625,12 @@ class BaseMySqlApp(service.BaseDbApp):
else:
network_mode = constants.DOCKER_BRIDGE_MODE
environment = {
"MYSQL_ROOT_PASSWORD": root_pass,
"MYSQL_INITDB_SKIP_TZINFO": 1}
if self._extra_envs:
environment.update(self._extra_envs)
try:
docker_util.start_container(
self.docker_client,
@@ -631,11 +639,10 @@ class BaseMySqlApp(service.BaseDbApp):
network_mode=network_mode,
ports=ports,
user=user,
environment={
"MYSQL_ROOT_PASSWORD": root_pass,
"MYSQL_INITDB_SKIP_TZINFO": 1,
},
command=command
environment=environment,
healthcheck=self.HEALTHCHECK,
command=command,
privileged=self._previledged
)
# Save root password

View File

@@ -342,6 +342,7 @@ class BaseDbStatus(object):
class BaseDbApp(object):
CFG_CODEC = stream_codecs.IniCodec()
HEALTHCHECK = None
def __init__(self, status, docker_client):
self.status = status

View File

@@ -14,9 +14,13 @@
# under the License.
#
import os
from oslo_log import log as logging
from trove.common import cfg
from trove.common import exception
from trove.guestagent.common import operating_system
from trove.guestagent.strategies.replication import mysql_base
CONF = cfg.CONF
@@ -39,6 +43,43 @@ class MariaDBGTIDReplication(mysql_base.MysqlReplicationBase):
return master_info
def read_last_master_gtid(self, service):
# mariadb > 10.5 uses mariadb_backup_binlog_info instead.
xtrabackup_info_file = ('%s/xtrabackup_binlog_info'
% service.get_data_dir())
mariabackup_info_file = ('%s/mariadb_backup_binlog_info'
% service.get_data_dir())
if os.path.exists(mariabackup_info_file):
INFO_FILE = mariabackup_info_file
LOG.info("Using mariabackup_info_file")
elif os.path.exists(xtrabackup_info_file):
INFO_FILE = xtrabackup_info_file
LOG.info("Using xtrabackup_binlog_info")
else:
# Handle the case where neither file exists
LOG.error("Neither xtrabackup_binlog_info nor "
"mariadb_backup_binlog_info found.")
raise exception.UnableToDetermineLastMasterGTID(
binlog_file="xtrabackup_binlog_info or"
"mariadb_backup_binlog_info")
operating_system.chmod(INFO_FILE,
operating_system.FileMode.ADD_READ_ALL,
as_root=True)
LOG.info("Reading last master GTID from %s", INFO_FILE)
try:
with open(INFO_FILE, 'r') as f:
content = f.read()
LOG.debug('Content in %s: "%s"', INFO_FILE, content)
ret = content.strip().split('\t')
return ret[2] if len(ret) == 3 else ''
except Exception as ex:
LOG.error('Failed to read last master GTID, error: %s', str(ex))
raise exception.UnableToDetermineLastMasterGTID(
binlog_file=INFO_FILE) from ex
def connect_to_master(self, service, master_info):
replica_conf = master_info['replica_conf']
last_gtid = ''

View File

@@ -135,7 +135,7 @@ class MysqlReplicationBase(base.Replication):
except Exception as ex:
LOG.error('Failed to read last master GTID, error: %s', str(ex))
raise exception.UnableToDetermineLastMasterGTID(
{'binlog_file': INFO_FILE})
{'binlog_file': INFO_FILE}) from ex
@abc.abstractmethod
def connect_to_master(self, service, master_info):

View File

@@ -95,6 +95,8 @@ def _create_container_with_low_level_api(image: str, param: dict) -> None:
networking_config = client.create_networking_config(
{param.get("network"):
client.create_endpoint_config(**network_config_kwargs)})
healthcheck = param.get("healthcheck", None)
# NOTE(wuchunyang): the low-level api doesn't support RUN interface,
# so we need pull image first, then start the container
LOG.debug("Pulling docker images: %s", image)
@@ -111,6 +113,7 @@ def _create_container_with_low_level_api(image: str, param: dict) -> None:
environment=param.get("environment"),
command=param.get("command"),
host_config=host_config,
healthcheck=healthcheck,
networking_config=networking_config)
LOG.debug("Starting container: %s", param.get("name"))
client.start(container=container)
@@ -119,7 +122,8 @@ def _create_container_with_low_level_api(image: str, param: dict) -> None:
def start_container(client, image, name="database",
restart_policy="unless-stopped",
volumes={}, ports={}, user="", network_mode="host",
environment={}, command=""):
environment={}, command="", healthcheck=None,
privileged=False):
"""Start a docker container.
:param client: docker client obj.
@@ -134,6 +138,7 @@ def start_container(client, image, name="database",
:param network_mode: One of bridge, none, host
:param environment: Environment variables
:param command:
:param privileged: docker privileged
:return:
"""
try:
@@ -151,12 +156,13 @@ def start_container(client, image, name="database",
f"command: {command}")
kwargs = dict(name=name,
restart_policy={"Name": restart_policy},
privileged=False,
privileged=privileged,
detach=True,
volumes=volumes,
ports=ports,
user=user,
environment=environment,
healthcheck=healthcheck,
command=command)
if network_mode == constants.DOCKER_HOST_NIC_MODE:
create_network(client, constants.DOCKER_NETWORK_NAME)
@@ -220,6 +226,17 @@ def get_container_status(client, name="database"):
return "unknown"
def get_container_health(client: docker.DockerClient, name="database") -> str:
try:
container = client.containers.get(name)
# One of staring, healthy, unhealthy, unknown
return container.health
except docker.errors.NotFound:
return "not running"
except Exception:
return "unknown"
def run_command(client, command, name="database"):
container = client.containers.get(name)
# output is Bytes type

View File

@@ -13,6 +13,8 @@
import json
from unittest import mock
import docker
from trove.guestagent.utils import docker as docker_utils
from trove.tests.unittests import trove_testtools
@@ -137,3 +139,23 @@ class TestDockerUtils(trove_testtools.TestCase):
mock_client().pull.assert_called_once()
mock_client().create_container.assert_called_once()
mock_client().start.assert_called_once()
@mock.patch("docker.DockerClient")
def test_get_health_status(self, mock_client):
mock_container = mock.MagicMock()
mock_container.health = "healthy"
mock_client.containers.get.return_value = mock_container
health_status = docker_utils.get_container_health(mock_client,
"test_container")
self.assertEqual(health_status, "healthy")
mock_client.containers.get.assert_called_once_with("test_container")
@mock.patch("docker.DockerClient")
def test_get_health_status_not_found(self, mock_client):
mock_client.containers.get.side_effect = docker.errors.NotFound(
"Container not found")
health_status = docker_utils.get_container_health(mock_client,
"test_container")
self.assertEqual(health_status, "not running")
mock_client.containers.get.assert_called_once_with("test_container")

View File

@@ -330,122 +330,6 @@
backup_wait_timeout: 1200
default_datastore_versions: mysql:8.0
- job:
name: trove-tempest-ubuntu-base-mariadb10.4
parent: trove-tempest-ubuntu-base
irrelevant-files:
- ^.*\.rst$
- ^api-ref/.*$
- ^doc/.*$
- ^etc/.*$
- ^releasenotes/.*$
- ^test-requirements.txt$
- ^tox.ini$
- ^LICENSE$
- ^contrib/
- ^zuul\.d/
- ^backup/
- ^\..+
- ^trove/guestagent/strategies/replication/
- ^trove/guestagent/datastore/(mysql.*|postgres)/.*$
vars:
devstack_localrc:
TROVE_DATASTORE_TYPE: mariadb
TROVE_DATASTORE_VERSION: 10.4
devstack_local_conf:
test-config:
$TEMPEST_CONFIG:
database:
enabled_datastores: "mariadb"
default_datastore_versions: mariadb:10.4
- job:
name: trove-tempest-ubuntu-backup-mariadb10.4
parent: trove-tempest-ubuntu-backup
irrelevant-files:
- ^.*\.rst$
- ^api-ref/.*$
- ^doc/.*$
- ^etc/.*$
- ^releasenotes/.*$
- ^test-requirements.txt$
- ^tox.ini$
- ^LICENSE$
- ^contrib/
- ^zuul\.d/
- ^\..+
- ^trove/guestagent/strategies/replication/
- ^trove/guestagent/datastore/(mysql.*|postgres)/.*$
vars:
devstack_localrc:
TROVE_DATASTORE_TYPE: mariadb
TROVE_DATASTORE_VERSION: 10.4
devstack_local_conf:
test-config:
$TEMPEST_CONFIG:
database:
enabled_datastores: "mariadb"
default_datastore_versions: mariadb:10.4
- job:
name: trove-tempest-ubuntu-replication-mariadb10.4
parent: trove-tempest-ubuntu-replication
irrelevant-files:
- ^.*\.rst$
- ^api-ref/.*$
- ^doc/.*$
- ^etc/.*$
- ^releasenotes/.*$
- ^test-requirements.txt$
- ^tox.ini$
- ^LICENSE$
- ^contrib/
- ^zuul\.d/
- ^backup/
- ^\..+
- ^trove/guestagent/datastore/(mysql.*|postgres)/.*$
- ^trove/guestagent/strategies/replication/(postgresql.*|mysql.*)\.py$
vars:
devstack_localrc:
TROVE_DATASTORE_TYPE: mariadb
TROVE_DATASTORE_VERSION: 10.4
devstack_local_conf:
test-config:
$TEMPEST_CONFIG:
database:
enabled_datastores: "mariadb"
default_datastore_versions: mariadb:10.4
- job:
name: trove-tempest-cinder-storage-driver-mariadb10.4
parent: trove-tempest-snapshot
irrelevant-files:
- ^.*\.rst$
- ^api-ref/.*$
- ^doc/.*$
- ^etc/.*$
- ^releasenotes/.*$
- ^test-requirements.txt$
- ^tox.ini$
- ^LICENSE$
- ^contrib/
- ^zuul\.d/
- ^backup/
- ^\..+
- ^trove/guestagent/datastore/(postgres|mysql)/.*$
- ^trove/guestagent/strategies/replication/(postgresql.*|mysql.*)\.py$
vars:
devstack_localrc:
TROVE_DATASTORE_TYPE: mariadb
TROVE_DATASTORE_VERSION: 10.4
devstack_local_conf:
test-config:
$TEMPEST_CONFIG:
database:
backup_wait_timeout: 1200
enabled_datastores: "mariadb"
default_datastore_versions: mariadb:10.4
- job:
name: trove-tempest-ubuntu-base-postgresql12
parent: trove-tempest-ubuntu-base

116
zuul.d/mariadb_jobs.yaml Normal file
View File

@@ -0,0 +1,116 @@
# mariadb jobs
- job:
name: trove-tempest-ubuntu-base-mariadb11.4
parent: trove-tempest-ubuntu-base
irrelevant-files:
- ^.*\.rst$
- ^api-ref/.*$
- ^doc/.*$
- ^etc/.*$
- ^releasenotes/.*$
- ^test-requirements.txt$
- ^tox.ini$
- ^LICENSE$
- ^contrib/
- ^zuul\.d/
- ^backup/
- ^\..+
- ^trove/guestagent/strategies/replication/
- ^trove/guestagent/datastore/(mysql.*|postgres)/.*$
vars:
devstack_localrc:
TROVE_DATASTORE_TYPE: mariadb
TROVE_DATASTORE_VERSION: 11.4
devstack_local_conf:
test-config:
$TEMPEST_CONFIG:
database:
enabled_datastores: "mariadb"
default_datastore_versions: mariadb:11.4
- job:
name: trove-tempest-ubuntu-backup-mariadb11.4
parent: trove-tempest-ubuntu-backup
irrelevant-files:
- ^.*\.rst$
- ^api-ref/.*$
- ^doc/.*$
- ^etc/.*$
- ^releasenotes/.*$
- ^test-requirements.txt$
- ^tox.ini$
- ^LICENSE$
- ^contrib/
- ^zuul\.d/
- ^\..+
- ^trove/guestagent/strategies/replication/
- ^trove/guestagent/datastore/(mysql.*|postgres)/.*$
vars:
devstack_localrc:
TROVE_DATASTORE_TYPE: mariadb
TROVE_DATASTORE_VERSION: 11.4
devstack_local_conf:
test-config:
$TEMPEST_CONFIG:
database:
enabled_datastores: "mariadb"
default_datastore_versions: mariadb:11.4
- job:
name: trove-tempest-ubuntu-replication-mariadb11.4
parent: trove-tempest-ubuntu-replication
irrelevant-files:
- ^.*\.rst$
- ^api-ref/.*$
- ^doc/.*$
- ^etc/.*$
- ^releasenotes/.*$
- ^test-requirements.txt$
- ^tox.ini$
- ^LICENSE$
- ^contrib/
- ^zuul\.d/
- ^backup/
- ^\..+
- ^trove/guestagent/datastore/(mysql.*|postgres)/.*$
- ^trove/guestagent/strategies/replication/(postgresql.*|mysql.*)\.py$
vars:
devstack_localrc:
TROVE_DATASTORE_TYPE: mariadb
TROVE_DATASTORE_VERSION: 11.4
devstack_local_conf:
test-config:
$TEMPEST_CONFIG:
database:
enabled_datastores: "mariadb"
default_datastore_versions: mariadb:11.4
- job:
name: trove-tempest-cinder-storage-driver-mariadb11.4
parent: trove-tempest-snapshot
irrelevant-files:
- ^.*\.rst$
- ^api-ref/.*$
- ^doc/.*$
- ^etc/.*$
- ^releasenotes/.*$
- ^test-requirements.txt$
- ^tox.ini$
- ^LICENSE$
- ^contrib/
- ^zuul\.d/
- ^backup/
- ^\..+
- ^trove/guestagent/datastore/(postgres|mysql)/.*$
- ^trove/guestagent/strategies/replication/(postgresql.*|mysql.*)\.py$
vars:
devstack_localrc:
TROVE_DATASTORE_TYPE: mariadb
TROVE_DATASTORE_VERSION: 11.4
devstack_local_conf:
test-config:
$TEMPEST_CONFIG:
database:
backup_wait_timeout: 1200
enabled_datastores: "mariadb"
default_datastore_versions: mariadb:11.4

View File

@@ -18,12 +18,13 @@
voting: false
- trove-tempest-cinder-storage-driver-mysql8.0:
voting: false
- trove-tempest-ubuntu-base-mariadb10.4
- trove-tempest-ubuntu-backup-mariadb10.4:
- trove-tempest-ubuntu-base-mariadb11.4:
voting: false
- trove-tempest-ubuntu-replication-mariadb10.4:
- trove-tempest-ubuntu-backup-mariadb11.4:
voting: false
- trove-tempest-cinder-storage-driver-mariadb10.4:
- trove-tempest-ubuntu-replication-mariadb11.4:
voting: false
- trove-tempest-cinder-storage-driver-mariadb11.4:
voting: false
- trove-tempest-ubuntu-base-postgresql12
- trove-tempest-ubuntu-backup-postgresql12:
@@ -41,7 +42,7 @@
gate:
jobs:
- trove-tempest-ubuntu-base-mysql8.0
- trove-tempest-ubuntu-base-mariadb10.4
- trove-tempest-ubuntu-base-mariadb11.4
- trove-tempest-ubuntu-base-postgresql12
experimental:
jobs: