You've already forked mariadb-columnstore-engine
mirror of
https://github.com/mariadb-corporation/mariadb-columnstore-engine.git
synced 2025-10-31 18:30:33 +03:00
Implements the initial upgrade capability across CMAPI and the CLI, including
repository setup, package operations, environment prechecks, and coordinated
cluster steps with progress reporting.
Details:
- CMAPI upgrade manager:
- Add `cmapi/cmapi_server/managers/upgrade/` modules:
- `repo.py`, `packages.py`, `preinstall.py`, `upgrade.py`, `utils.py` and `__init__.py`
- Extend endpoints and routing to expose upgrade operations and status:
- `cmapi_server/controllers/{endpoints.py, dispatcher.py, api_clients.py}`
- `cmapi_server/managers/{application.py, process.py}`
- Add improved constants and helpers for upgrade flow
- Backup/restore and safety:
- Add `cmapi_server/managers/backup_restore.py`
- Fix pre-upgrade backup regressions (due to `mcs_backup_manager.sh 3.17 changes`)
- Improve cluster version validation; add `ignore_missmatch` override
- CLI enhancements:
- Progress UI and richer feedback (`mcs_cluster_tool/tools_commands.py`, `README.md`, `mcs.1`)
- Add steps to start MDB and start MCS during/after upgrade
- Improved error surfacing for version validation
- Platform and packaging:
- Ubuntu and Rocky Linux support
- RHEL/DNF dry-run support
- Distro detection and platform-dependent logic hardened
- Logging improvements
- Updater service:
- Add `cmapi/updater/cmapi_updater.service.template` and `cmapi_updater.sh` to make CMAPI update itself
- Docs:
- Update mcs cli README and mcs.1 man file
- Add `cmapi/updater/README.md`
289 lines
11 KiB
Python
289 lines
11 KiB
Python
import logging
|
|
import os
|
|
import re
|
|
import subprocess
|
|
|
|
import requests
|
|
|
|
from cmapi_server.constants import (
|
|
ES_REPO, ES_REPO_PRIORITY_PREFS, ES_TOKEN_VERIFY_URL, ES_VERIFY_URL, MDB_GPG_KEY_URL,
|
|
MDB_LATEST_RELEASES_URL, MDB_LATEST_TESTED_MAJOR, REQUEST_TIMEOUT,
|
|
)
|
|
from cmapi_server.exceptions import CMAPIBasicError
|
|
from cmapi_server.managers.upgrade.utils import ComparableVersion
|
|
from cmapi_server.process_dispatchers.base import BaseDispatcher
|
|
|
|
|
|
class MariaDBESRepoManager:
|
|
def __init__(
|
|
self, token: str, arch: str, os_type: str, os_version: str,
|
|
mariadb_version: str = 'latest',
|
|
):
|
|
self.token = token
|
|
self.arch = arch
|
|
self.os_type = os_type
|
|
self.os_version = os_version
|
|
if mariadb_version == 'latest':
|
|
self.mariadb_version = self.get_latest_tested_mdb_version()
|
|
else:
|
|
self.mariadb_version = mariadb_version
|
|
|
|
def _import_mariadb_keyring(self):
|
|
"""
|
|
Download and place the MariaDB keyring into /etc/apt/trusted.gpg.d.
|
|
"""
|
|
key_url = 'https://supplychain.mariadb.com/mariadb-keyring-2019.gpg'
|
|
keyring_path = '/etc/apt/trusted.gpg.d/mariadb-keyring-2019.gpg'
|
|
|
|
try:
|
|
# Download the keyring file
|
|
response = requests.get(key_url)
|
|
response.raise_for_status()
|
|
|
|
# Write the keyring file to the specified path
|
|
with open(keyring_path, 'wb') as key_file:
|
|
key_file.write(response.content)
|
|
|
|
# Set permissions to 644
|
|
os.chmod(keyring_path, 0o644)
|
|
logging.debug(
|
|
f'Keyring successfully downloaded and placed at {keyring_path}'
|
|
)
|
|
except requests.RequestException as exc:
|
|
raise CMAPIBasicError(
|
|
f'Failed to download keyring from {key_url}: {exc}'
|
|
)
|
|
except OSError as exc:
|
|
raise CMAPIBasicError(
|
|
f'Failed to write keyring to {keyring_path}: {exc}'
|
|
)
|
|
|
|
def check_mdb_version_exists(self):
|
|
"""Check if passed MDB version exists in the repo.
|
|
|
|
:raises CMAPIBasicError: unsupported OS type
|
|
:raises CMAPIBasicError: wrong MDB version passed
|
|
:raises CMAPIBasicError: some other request/response errors
|
|
"""
|
|
verify_url: str = ''
|
|
if self.os_type in ['ubuntu', 'debian']:
|
|
# get only two first numbers from version to build repo link
|
|
verify_url = ES_VERIFY_URL.deb.format(
|
|
token=self.token,
|
|
mdb_version=self.mariadb_version,
|
|
os_version=self.os_version
|
|
)
|
|
elif self.os_type in ['centos', 'rhel', 'rocky']:
|
|
verify_url = ES_VERIFY_URL.rhel.format(
|
|
token=self.token,
|
|
mdb_version=self.mariadb_version,
|
|
os_major_version=self.os_version.split('.', maxsplit=1)[0],
|
|
arch=self.arch
|
|
)
|
|
else:
|
|
raise CMAPIBasicError(f'Unsupported OS type: {self.os_type}')
|
|
try:
|
|
# Download the keyring file
|
|
response = requests.get(verify_url, timeout=REQUEST_TIMEOUT)
|
|
if response.status_code in (403, 404):
|
|
raise CMAPIBasicError(
|
|
'MariaDB Enterprise Server version '
|
|
f'{self.mariadb_version} is not working for your OS '
|
|
'version or OS type.\nPlease verify that it is correct.\n '
|
|
'Not all releases of MariaDB are available on all '
|
|
'distributions.'
|
|
)
|
|
elif response.ok:
|
|
logging.debug(
|
|
'MariaDB Enterprise Server version '
|
|
f'{self.mariadb_version} is valid.'
|
|
)
|
|
else:
|
|
response.raise_for_status()
|
|
except requests.RequestException:
|
|
raise CMAPIBasicError(
|
|
'Failed to check MDB version exists from '
|
|
f'{verify_url}'
|
|
)
|
|
|
|
@staticmethod
|
|
def verify_token(token: str):
|
|
"""Verify ES token.
|
|
|
|
:param token: es token to verify
|
|
:type token: str
|
|
:raises CMAPIBasicError: Invalid token format
|
|
:raises CMAPIBasicError: Invalid token
|
|
:raises CMAPIBasicError: Other request errors
|
|
"""
|
|
# Check token format UUID
|
|
valid_format = re.fullmatch(
|
|
r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}',
|
|
token
|
|
)
|
|
if not valid_format:
|
|
err_message = (
|
|
f'Invalid token format: "{token}". The token should be '
|
|
'of the form ########-####-####-####-############'
|
|
)
|
|
raise CMAPIBasicError(err_message)
|
|
|
|
verify_url = ES_TOKEN_VERIFY_URL.format(token=token)
|
|
|
|
try:
|
|
response = requests.head(
|
|
verify_url, allow_redirects=True, timeout=REQUEST_TIMEOUT
|
|
)
|
|
if response.status_code in (403, 404):
|
|
raise CMAPIBasicError(
|
|
'Invalid token. Please verify that it is correct.'
|
|
)
|
|
elif response.ok:
|
|
logging.debug('MariaDB ES Token is valid.')
|
|
else:
|
|
response.raise_for_status()
|
|
except requests.RequestException:
|
|
raise CMAPIBasicError(
|
|
'Problem encountered while trying to verify ES token.'
|
|
)
|
|
|
|
@classmethod
|
|
def get_latest_tested_mdb_version(cls) -> str:
|
|
"""Get latest tested MDB version from repo.
|
|
|
|
:raises CMAPIBasicError: no latest version matched with latest tested
|
|
:raises CMAPIBasicError: if request error
|
|
:return: latest MDB version matched with latest tested major
|
|
:rtype: str
|
|
"""
|
|
try:
|
|
# Download the keyring file
|
|
response = requests.get(
|
|
MDB_LATEST_RELEASES_URL, timeout=REQUEST_TIMEOUT
|
|
)
|
|
response.raise_for_status()
|
|
latest_version_nums = [
|
|
ver
|
|
for ver in response.text.split(' ')
|
|
if MDB_LATEST_TESTED_MAJOR in ver
|
|
]
|
|
if not latest_version_nums:
|
|
raise CMAPIBasicError(
|
|
'Failed to get latest MDB version number matched latest '
|
|
f'tested major {MDB_LATEST_TESTED_MAJOR}'
|
|
)
|
|
latest_version_num = sorted(latest_version_nums, reverse=True)[0]
|
|
logging.debug(
|
|
'Succesfully got latest MBD version number: '
|
|
f'{latest_version_num}'
|
|
)
|
|
except requests.RequestException as exc:
|
|
raise CMAPIBasicError(
|
|
'Failed to get latest MDB version numbers from '
|
|
f'{MDB_LATEST_RELEASES_URL}'
|
|
)
|
|
return latest_version_num
|
|
|
|
@classmethod
|
|
def get_ver_of(cls, package_name: str, os_type: str,) -> str:
|
|
"""Get version of package in a repo.
|
|
|
|
:param package_name: name of package to get version
|
|
:type package_name: str
|
|
:param os_type: os name
|
|
:type os_type: str
|
|
:raises CMAPIBasicError: if os type isn't supported
|
|
:raises CMAPIBasicError: if failed getting package info
|
|
:raises CMAPIBasicError: if couldn't find any package with given name
|
|
:return: latest available package version
|
|
:rtype: str
|
|
"""
|
|
latest_version: str = ''
|
|
cmd: str = ''
|
|
if os_type in ['ubuntu', 'debian']:
|
|
cmd = f'apt show {package_name}'
|
|
elif os_type in ['centos', 'rhel', 'rocky']:
|
|
cmd = f'yum info --showduplicates --available {package_name}'
|
|
else:
|
|
raise CMAPIBasicError(f'Unsupported OS type: {os_type}')
|
|
|
|
success, result = BaseDispatcher.exec_command(cmd)
|
|
if not success:
|
|
message = (
|
|
f'Failed to get {package_name} package information using '
|
|
f'command {cmd} with result: {result}'
|
|
)
|
|
logging.error(message)
|
|
raise CMAPIBasicError(message)
|
|
|
|
matches = re.findall(r'\bVersion\s*:\s+(\S+)', result)
|
|
if matches:
|
|
latest_version = max(matches, key=ComparableVersion)
|
|
else:
|
|
raise CMAPIBasicError(
|
|
'Could not find any version for package with name '
|
|
f'{package_name}'
|
|
)
|
|
return latest_version
|
|
|
|
def check_repo(self):
|
|
"""Check repo installed and have needed version of MDB.
|
|
|
|
:raises CMAPIBasicError: if unsupported os type detected
|
|
:raises CMAPIBasicError: could not find package matching the version
|
|
"""
|
|
pkg_ver: str = ''
|
|
mdb_pkg_mgr_version = self.get_ver_of('mariadb-server', self.os_type)
|
|
if self.os_type in ['ubuntu', 'debian']:
|
|
# for deb packages it's just a part of version
|
|
# eg: 10.6.22.18 and 1:10.6.22.18+maria~ubu2204
|
|
pkg_ver = self.mariadb_version.replace('-', '.')
|
|
elif self.os_type in ['centos', 'rhel', 'rocky']:
|
|
# in rhel distros it's full version
|
|
pkg_ver = self.mariadb_version.replace('-', '_')
|
|
else:
|
|
raise CMAPIBasicError(f'Unsupported OS type: {self.os_type}')
|
|
if pkg_ver not in mdb_pkg_mgr_version:
|
|
raise CMAPIBasicError(
|
|
'Could not find mariadb-server package matched with version '
|
|
f'{pkg_ver}'
|
|
)
|
|
|
|
def setup_repo(self):
|
|
"""Set up the MariaDB Enterprise repository based on OS type."""
|
|
self.check_mdb_version_exists()
|
|
if self.os_type in ['ubuntu', 'debian']:
|
|
# get only two first numbers from version to build repo link
|
|
repo_data = ES_REPO.deb.format(
|
|
token=self.token,
|
|
mdb_version=self.mariadb_version,
|
|
os_version=self.os_version
|
|
)
|
|
repo_file = '/etc/apt/sources.list.d/mariadb.list'
|
|
with open(repo_file, 'w', encoding='utf-8') as f:
|
|
f.write(repo_data)
|
|
# Set permissions to 640
|
|
os.chmod(repo_file, 0o640)
|
|
|
|
pref_file = '/etc/apt/preferences'
|
|
with open(pref_file, 'w', encoding='utf-8') as f:
|
|
f.write(ES_REPO_PRIORITY_PREFS)
|
|
|
|
self._import_mariadb_keyring()
|
|
subprocess.run(['apt-get', 'update'], check=True)
|
|
elif self.os_type in ['centos', 'rhel', 'rocky']:
|
|
repo_data = ES_REPO.rhel.format(
|
|
token=self.token,
|
|
mdb_version=self.mariadb_version,
|
|
os_major_version=self.os_version.split('.', maxsplit=1)[0],
|
|
arch=self.arch,
|
|
gpg_key_url=MDB_GPG_KEY_URL
|
|
)
|
|
repo_file = '/etc/yum.repos.d/mariadb.repo'
|
|
with open(repo_file, 'w', encoding='utf-8') as f:
|
|
f.write(repo_data)
|
|
subprocess.run(['rpm', '--import', MDB_GPG_KEY_URL], check=True)
|
|
else:
|
|
raise CMAPIBasicError(f'Unsupported OS type: {self.os_type}')
|
|
self.check_repo()
|