1
0
mirror of https://github.com/mariadb-corporation/mariadb-columnstore-engine.git synced 2025-11-02 06:13:16 +03:00
Files
mariadb-columnstore-engine/cmapi/cmapi_server/test/test_em_endpoints.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

227 lines
7.9 KiB
Python

import configparser
import subprocess
import unittest
from contextlib import contextmanager
from os import path, remove
from pathlib import Path
from shutil import copyfile
import cherrypy
import requests
requests.packages.urllib3.disable_warnings()
from cmapi_server.constants import (
EM_PATH_SUFFIX, MCS_EM_PATH, MCS_BRM_CURRENT_PATH, S3_BRM_CURRENT_PATH, _version
)
from cmapi_server.controllers.dispatcher import dispatcher, jsonify_error
from cmapi_server.managers.certificate import CertificateManager
from cmapi_server.test.unittest_global import (
cmapi_config_filename, tmp_cmapi_config_filename
)
from mcs_node_control.models.node_config import NodeConfig
@contextmanager
def run_server():
CertificateManager.create_self_signed_certificate_if_not_exist()
cherrypy.engine.start()
cherrypy.engine.wait(cherrypy.engine.states.STARTED)
yield
cherrypy.engine.exit()
cherrypy.engine.block()
def get_current_key():
app_config = configparser.ConfigParser()
try:
with open(cmapi_config_filename, 'r') as _config_file:
app_config.read_file(_config_file)
except FileNotFoundError:
return ''
if 'Authentication' not in app_config.sections():
return ''
return app_config['Authentication'].get('x-api-key', '')
class TestEMEndpoints(unittest.TestCase):
@classmethod
def setUpClass(cls):
if not path.exists(tmp_cmapi_config_filename):
f = open(tmp_cmapi_config_filename, 'x')
f.close()
copyfile(cmapi_config_filename, tmp_cmapi_config_filename)
@classmethod
def tearDownClass(cls):
if path.exists(tmp_cmapi_config_filename):
copyfile(tmp_cmapi_config_filename, cmapi_config_filename)
remove(tmp_cmapi_config_filename)
def get_examplar_bytes(self, element: str):
node_config = NodeConfig()
if node_config.s3_enabled():
ret = subprocess.run(
["smcat", S3_BRM_CURRENT_PATH], stdout=subprocess.PIPE
)
element_current_suffix = ret.stdout.decode("utf-8").rstrip()
suffix_for_file = element_current_suffix
# Journal is always singular, so strip trailing A/B from suffix
if element == 'journal' and suffix_for_file.endswith(('A', 'B')):
suffix_for_file = suffix_for_file[:-1]
element_current_filename = f'{EM_PATH_SUFFIX}/{suffix_for_file}_{element}'
ret = subprocess.run(
["smcat", element_current_filename], stdout=subprocess.PIPE
)
result = ret.stdout
else:
element_current_name = Path(MCS_BRM_CURRENT_PATH)
element_current_filename = element_current_name.read_text().rstrip()
suffix_for_file = element_current_filename
# Journal is always singular, so strip trailing A/B from suffix
if element == 'journal' and suffix_for_file.endswith(('A', 'B')):
suffix_for_file = suffix_for_file[:-1]
element_current_file = Path(
f'{MCS_EM_PATH}/{suffix_for_file}_{element}'
)
result = element_current_file.read_bytes()
return result
def test_em(self):
app = cherrypy.tree.mount(root=None,
config=cmapi_config_filename)
app.config.update({
'/': {
'request.dispatch': dispatcher,
'error_page.default': jsonify_error,
},
'config': {
'path': cmapi_config_filename,
},
})
cherrypy.config.update(cmapi_config_filename)
api_key = get_current_key()
try:
with run_server():
url = f"https://localhost:8640/cmapi/{_version}/node/meta/em"
# Auth failure
headers = {'x-api-key': None}
r = requests.get(url, verify=False, headers=headers)
self.assertEqual(r.status_code, 401)
# OK
headers = {'x-api-key': api_key}
r = requests.get(url, verify=False, headers=headers)
extent_map = self.get_examplar_bytes('em')
self.assertEqual(r.status_code, 200)
self.assertEqual(r.content, extent_map)
except:
cherrypy.engine.exit()
cherrypy.engine.block()
raise
def test_journal(self):
app = cherrypy.tree.mount(root=None,
config=cmapi_config_filename)
app.config.update({
'/': {
'request.dispatch': dispatcher,
'error_page.default': jsonify_error,
},
'config': {
'path': cmapi_config_filename,
},
})
cherrypy.config.update(cmapi_config_filename)
api_key = get_current_key()
try:
with run_server():
url = f"https://localhost:8640/cmapi/{_version}/node/meta/journal"
# Auth failure
headers = {'x-api-key': None}
r = requests.get(url, verify=False, headers=headers)
self.assertEqual(r.status_code, 401)
# OK
headers = {'x-api-key': api_key}
r = requests.get(url, verify=False, headers=headers)
journal = self.get_examplar_bytes('journal')
self.assertEqual(r.status_code, 200)
self.assertEqual(r.content, journal)
except:
cherrypy.engine.exit()
cherrypy.engine.block()
raise
def test_vss(self):
app = cherrypy.tree.mount(root=None,
config=cmapi_config_filename)
app.config.update({
'/': {
'request.dispatch': dispatcher,
'error_page.default': jsonify_error,
},
'config': {
'path': cmapi_config_filename,
},
})
cherrypy.config.update(cmapi_config_filename)
api_key = get_current_key()
try:
with run_server():
url = f"https://localhost:8640/cmapi/{_version}/node/meta/vss"
# Auth failure
headers = {'x-api-key': None}
r = requests.get(url, verify=False, headers=headers)
self.assertEqual(r.status_code, 401)
# OK
headers = {'x-api-key': api_key}
r = requests.get(url, verify=False, headers=headers)
vss = self.get_examplar_bytes('vss')
self.assertEqual(r.status_code, 200)
self.assertEqual(r.content, vss)
except:
cherrypy.engine.exit()
cherrypy.engine.block()
raise
def test_vbbm(self):
app = cherrypy.tree.mount(root=None,
config=cmapi_config_filename)
app.config.update({
'/': {
'request.dispatch': dispatcher,
'error_page.default': jsonify_error,
},
'config': {
'path': cmapi_config_filename,
},
})
cherrypy.config.update(cmapi_config_filename)
api_key = get_current_key()
try:
with run_server():
url = f"https://localhost:8640/cmapi/{_version}/node/meta/vbbm"
# Auth failure
headers = {'x-api-key': None}
r = requests.get(url, verify=False, headers=headers)
self.assertEqual(r.status_code, 401)
# OK
headers = {'x-api-key': api_key}
r = requests.get(url, verify=False, headers=headers)
vbbm = self.get_examplar_bytes('vbbm')
self.assertEqual(r.status_code, 200)
self.assertEqual(r.content, vbbm)
except:
cherrypy.engine.exit()
cherrypy.engine.block()
raise