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`
198 lines
6.9 KiB
Python
198 lines
6.9 KiB
Python
import logging
|
|
import platform
|
|
from typing import Optional, Tuple, Dict
|
|
|
|
import distro
|
|
|
|
from cmapi_server.constants import (
|
|
MDB_CS_PACKAGE_NAME, MDB_SERVER_PACKAGE_NAME, PKG_GET_VER_CMD,
|
|
SUPPORTED_DISTROS, SUPPORTED_ARCHITECTURES, VERSION_PATH, MultiDistroNamer
|
|
)
|
|
from cmapi_server.exceptions import CMAPIBasicError
|
|
from cmapi_server.process_dispatchers.base import BaseDispatcher
|
|
|
|
|
|
class AppManager:
|
|
started: bool = False
|
|
version: Optional[str] = None
|
|
git_revision: Optional[str] = None
|
|
|
|
@classmethod
|
|
def get_version(cls) -> str:
|
|
if cls.version:
|
|
return cls.version
|
|
version, revision = cls._read_version_file()
|
|
cls.version = version
|
|
cls.git_revision = revision
|
|
return cls.version
|
|
|
|
@classmethod
|
|
def get_git_revision(cls) -> Optional[str]:
|
|
if cls.git_revision is not None:
|
|
return cls.git_revision
|
|
_, revision = cls._read_version_file()
|
|
cls.git_revision = revision
|
|
return cls.git_revision
|
|
|
|
@classmethod
|
|
def _read_version_file(cls) -> Tuple[str, Optional[str]]:
|
|
"""Read structured values from VERSION file.
|
|
|
|
Returns tuple: (semantic_version, git_revision or None)
|
|
"""
|
|
values: Dict[str, str] = {}
|
|
try:
|
|
with open(VERSION_PATH, encoding='utf-8') as version_file:
|
|
for line in version_file.read().splitlines():
|
|
if not line or '=' not in line:
|
|
continue
|
|
key, val = line.strip().split('=', 1)
|
|
values[key.strip()] = val.strip()
|
|
except Exception:
|
|
logging.exception("Failed to read VERSION file")
|
|
return 'Undefined', None
|
|
|
|
# Release (build) part is optional
|
|
release = values.get('CMAPI_VERSION_RELEASE')
|
|
revision = values.get('CMAPI_GIT_REVISION')
|
|
|
|
required_keys = (
|
|
'CMAPI_VERSION_MAJOR',
|
|
'CMAPI_VERSION_MINOR',
|
|
'CMAPI_VERSION_PATCH',
|
|
)
|
|
if not all(k in values and values[k] for k in required_keys):
|
|
logging.error("Couldn't detect version from VERSION file!")
|
|
return 'Undefined', revision
|
|
|
|
version = '.'.join([
|
|
values['CMAPI_VERSION_MAJOR'],
|
|
values['CMAPI_VERSION_MINOR'],
|
|
values['CMAPI_VERSION_PATCH'],
|
|
])
|
|
if release:
|
|
version = f"{version}.{release}"
|
|
return version, revision
|
|
|
|
@classmethod
|
|
def get_architecture(cls) -> str:
|
|
"""Get system architecture.
|
|
|
|
:return: system architecture
|
|
:rtype: str
|
|
"""
|
|
arch = platform.machine().lower()
|
|
if arch not in SUPPORTED_ARCHITECTURES:
|
|
message = f'Unsupported architecture: {arch}'
|
|
logging.error(message)
|
|
raise CMAPIBasicError(message)
|
|
if arch in ['x86_64', 'amd64']:
|
|
arch = 'x86_64'
|
|
elif arch in ['aarch64', 'arm64']:
|
|
arch = 'aarch64'
|
|
return arch
|
|
|
|
@classmethod
|
|
def get_distro_info(cls) -> tuple[str, str]:
|
|
"""Get OS name and version.
|
|
|
|
:return: OS name and version
|
|
:rtype: tuple[str, str]
|
|
"""
|
|
platform_name = platform.system().lower()
|
|
if platform_name == 'linux':
|
|
distro_name = distro.id().lower()
|
|
distro_version = distro.version().lower()
|
|
if distro_name == 'debian':
|
|
if distro_version.startswith('11'):
|
|
distro_version = 'bullseye'
|
|
elif distro_version.startswith('12'):
|
|
distro_version = 'bookworm'
|
|
else:
|
|
message = (
|
|
f'Unsupported Debian version: {distro_version}. '
|
|
'Supported versions are 11 (bullseye) and 12 '
|
|
'(bookworm).'
|
|
)
|
|
logging.error(message)
|
|
raise CMAPIBasicError(message)
|
|
if distro_name == 'ubuntu':
|
|
if distro_version.startswith('20.04'):
|
|
distro_version = 'focal'
|
|
elif distro_version.startswith('22.04'):
|
|
distro_version = 'jammy'
|
|
elif distro_version.startswith('24.04'):
|
|
distro_version = 'noble'
|
|
else:
|
|
message = (
|
|
f'Unsupported Ubuntu version: {distro_version}. '
|
|
'Supported versions are 20.04 (focal), 22.04 (jammy), '
|
|
'and 24.04 (noble).'
|
|
)
|
|
logging.error(message)
|
|
raise CMAPIBasicError(message)
|
|
if distro_name not in SUPPORTED_DISTROS:
|
|
message = (
|
|
f'Unsupported Linux distribution: {distro_name}. '
|
|
)
|
|
logging.error(message)
|
|
raise CMAPIBasicError(message)
|
|
else:
|
|
message = f'Unsupported platform: {platform_name}'
|
|
logging.error(message)
|
|
raise CMAPIBasicError(message)
|
|
return distro_name, distro_version
|
|
|
|
@classmethod
|
|
def get_installed_pkg_ver(cls, pkg_namer: MultiDistroNamer) -> str:
|
|
"""Get package version with given package name.
|
|
|
|
:param pkg_namer: object that contains package name for several ditros
|
|
:type pkg_namer: MultiDistroNamer
|
|
:raises CMAPIBasicError: if failed getting version
|
|
:return: package version
|
|
:rtype: str
|
|
"""
|
|
distro_name, _ = cls.get_distro_info()
|
|
cmd: str = ''
|
|
package_name: str = ''
|
|
if distro_name in ['ubuntu', 'debian']:
|
|
package_name = pkg_namer.deb
|
|
cmd = PKG_GET_VER_CMD.deb.format(package_name=package_name)
|
|
elif distro_name in ['centos', 'rhel', 'rocky', 'almalinux']:
|
|
package_name = pkg_namer.rhel
|
|
cmd = PKG_GET_VER_CMD.rhel.format(package_name=package_name)
|
|
success, result_raw = BaseDispatcher.exec_command(cmd)
|
|
if not success:
|
|
message = (
|
|
f'Failed to get {package_name} version with result: '
|
|
f'{result_raw}'
|
|
)
|
|
logging.error(message)
|
|
raise CMAPIBasicError(message)
|
|
version_clean = result_raw
|
|
if distro_name in ['ubuntu', 'debian']:
|
|
# remove prefix before : (epoch)
|
|
result_raw = result_raw.split(':', 1)[1]
|
|
# remove suffix after first '+'
|
|
version_clean = result_raw.split('+', 1)[0]
|
|
return version_clean
|
|
|
|
@classmethod
|
|
def get_mdb_version(cls) -> str:
|
|
"""Get MDB version.
|
|
|
|
:return: MDB version
|
|
:rtype: str
|
|
"""
|
|
return cls.get_installed_pkg_ver(MDB_SERVER_PACKAGE_NAME)
|
|
|
|
@classmethod
|
|
def get_columnstore_version(cls) -> str:
|
|
"""Get Columnstore version.
|
|
|
|
:return: Columnstore version
|
|
:rtype: str
|
|
"""
|
|
return cls.get_installed_pkg_ver(MDB_CS_PACKAGE_NAME)
|