1
0
mirror of https://github.com/mariadb-corporation/mariadb-columnstore-engine.git synced 2025-10-31 18:30:33 +03:00
Files
mariadb-columnstore-engine/cmapi/cmapi_server/managers/upgrade/repo.py
mariadb-AlanMologorsky a76e153a1d feat(upgrade): MCOL-6028 upgrade MVP with repo-managed flow, prechecks, and other enhancements
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`
2025-09-30 18:48:32 +04:00

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()