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/application.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

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)