Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions cmapi/cmapi_server/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""
import os
from typing import NamedTuple
from enum import Enum


# default MARIADB ColumnStore config path
Expand Down Expand Up @@ -60,6 +61,15 @@
CMAPI_INSTALL_PATH, 'cmapi_server/SingleNode.xml'
)

class MCSProgs(Enum):
STORAGE_MANAGER = 'StorageManager'
WORKER_NODE = 'workernode'
CONTROLLER_NODE = 'controllernode'
PRIM_PROC = 'PrimProc'
WRITE_ENGINE_SERVER = 'WriteEngineServer'
DML_PROC = 'DMLProc'
DDL_PROC = 'DDLProc'

# constants for dispatchers
class ProgInfo(NamedTuple):
"""NamedTuple for some additional info about handling mcs processes."""
Expand All @@ -73,17 +83,16 @@ class ProgInfo(NamedTuple):
# on top level of process handling
# mcs-storagemanager starts conditionally inside mcs-loadbrm, but should be
# stopped using cmapi
ALL_MCS_PROGS = {
ALL_MCS_PROGS: dict[MCSProgs, ProgInfo] = {
# workernode starts on primary and non primary node with 1 or 2 added
# to subcommand (DBRM_Worker1 - on primary, DBRM_Worker2 - non primary)
'StorageManager': ProgInfo(15, 'mcs-storagemanager', '', False, 1),
'workernode': ProgInfo(13, 'mcs-workernode', 'DBRM_Worker{}', False, 1),
'controllernode': ProgInfo(11, 'mcs-controllernode', 'fg', True),
'PrimProc': ProgInfo(5, 'mcs-primproc', '', False, 1),
'ExeMgr': ProgInfo(9, 'mcs-exemgr', '', False, 1),
'WriteEngineServer': ProgInfo(7, 'mcs-writeengineserver', '', False, 3),
'DMLProc': ProgInfo(3, 'mcs-dmlproc', '', False),
'DDLProc': ProgInfo(1, 'mcs-ddlproc', '', False),
MCSProgs.STORAGE_MANAGER: ProgInfo(15, 'mcs-storagemanager', '', False, 1),
MCSProgs.WORKER_NODE: ProgInfo(13, 'mcs-workernode', 'DBRM_Worker{}', False, 1),
MCSProgs.CONTROLLER_NODE: ProgInfo(11, 'mcs-controllernode', 'fg', True),
MCSProgs.PRIM_PROC: ProgInfo(5, 'mcs-primproc', '', False, 1),
MCSProgs.WRITE_ENGINE_SERVER: ProgInfo(7, 'mcs-writeengineserver', '', False, 3),
MCSProgs.DML_PROC: ProgInfo(3, 'mcs-dmlproc', '', False),
MCSProgs.DDL_PROC: ProgInfo(1, 'mcs-ddlproc', '', False),
}

# constants for docker container dispatcher
Expand Down
1 change: 1 addition & 0 deletions cmapi/cmapi_server/controllers/api_clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def add_node(
:param node_info: Information about the node to add.
:return: The response from the API.
"""
#TODO: fix interface as in remove_node used or think about universal
return self._request('PUT', 'node', {**node_info, **extra})

def remove_node(
Expand Down
17 changes: 12 additions & 5 deletions cmapi/cmapi_server/controllers/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ def put_config(self):
MCSProcessManager.stop_node(
is_primary=node_config.is_primary_node(),
use_sudo=use_sudo,
timeout=request_timeout
timeout=request_timeout,
)
except CMAPIBasicError as err:
raise_422_error(
Expand Down Expand Up @@ -463,6 +463,7 @@ def put_config(self):
MCSProcessManager.start_node(
is_primary=node_config.is_primary_node(),
use_sudo=use_sudo,
is_read_only=node_config.is_read_only(),
)
except CMAPIBasicError as err:
raise_422_error(
Expand Down Expand Up @@ -666,7 +667,8 @@ def put_start(self):
try:
MCSProcessManager.start_node(
is_primary=node_config.is_primary_node(),
use_sudo=use_sudo
use_sudo=use_sudo,
is_read_only=node_config.is_read_only(),
)
except CMAPIBasicError as err:
raise_422_error(
Expand Down Expand Up @@ -701,7 +703,7 @@ def put_shutdown(self):
MCSProcessManager.stop_node(
is_primary=node_config.is_primary_node(),
use_sudo=use_sudo,
timeout=timeout
timeout=timeout,
)
except CMAPIBasicError as err:
raise_422_error(
Expand Down Expand Up @@ -910,16 +912,21 @@ def put_add_node(self):
node = request_body.get('node', None)
config = request_body.get('config', DEFAULT_MCS_CONF_PATH)
in_transaction = request_body.get('in_transaction', False)
read_only = request_body.get('read_only', False)
add_other_nodes_dbroots = request_body.get('add_other_nodes_dbroots', True)

if node is None:
raise_422_error(module_logger, func_name, 'missing node argument')

if not read_only and ('add_other_nodes_dbroots' in request_body):
raise_422_error(module_logger, func_name, 'add_other_nodes_dbroots is only valid when read_only is true')

try:
if not in_transaction:
with TransactionManager(extra_nodes=[node]):
response = ClusterHandler.add_node(node, config)
response = ClusterHandler.add_node(node, config, read_only, add_other_nodes_dbroots)
else:
response = ClusterHandler.add_node(node, config)
response = ClusterHandler.add_node(node, config, read_only, add_other_nodes_dbroots)
except CMAPIBasicError as err:
raise_422_error(module_logger, func_name, err.message)

Expand Down
39 changes: 30 additions & 9 deletions cmapi/cmapi_server/handlers/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
get_current_key, get_version, update_revision_and_manager,
)
from cmapi_server.node_manipulation import (
add_node, add_dbroot, remove_node, switch_node_maintenance,
add_node, add_dbroot, remove_node, switch_node_maintenance, update_dbroots_of_readonly_nodes,
)
from mcs_node_control.models.misc import get_dbrm_master
from mcs_node_control.models.node_config import NodeConfig
Expand Down Expand Up @@ -50,7 +50,7 @@ def toggle_cluster_state(
broadcast_new_config(config, distribute_secrets=True)


class ClusterHandler():
class ClusterHandler:
"""Class for handling MCS Cluster operations."""

@staticmethod
Expand Down Expand Up @@ -140,14 +140,22 @@ def shutdown(
return {'timestamp': operation_start_time}

@staticmethod
def add_node(node: str, config: str = DEFAULT_MCS_CONF_PATH) -> dict:
def add_node(
node: str, config: str = DEFAULT_MCS_CONF_PATH,
read_only: bool = False,
add_other_nodes_dbroots: bool = True,
) -> dict:
"""Method to add node to MCS CLuster.

:param node: node IP or name or FQDN
:type node: str
:param config: columnstore xml config file path,
defaults to DEFAULT_MCS_CONF_PATH
:type config: str, optional
:param read_only: add node in read-only mode (not starting write engine), defaults to False
:type read_only: bool, optional
:param add_other_nodes_dbroots: for read-only nodes, whether to add dbroots of other nodes
:type add_other_nodes_dbroots: bool, optional
:raises CMAPIBasicError: on exception while starting transaction
:raises CMAPIBasicError: if transaction start isn't successful
:raises CMAPIBasicError: on exception while adding node
Expand All @@ -158,20 +166,29 @@ def add_node(node: str, config: str = DEFAULT_MCS_CONF_PATH) -> dict:
:rtype: dict
"""
logger: logging.Logger = logging.getLogger('cmapi_server')
logger.debug(f'Cluster add node command called. Adding node {node}.')
logger.debug(
f'Cluster add node command called. Adding node {node} in '
f'{"read-only" if read_only else "read-write"} mode.'
)

response = {'timestamp': str(datetime.now())}

if not read_only and add_other_nodes_dbroots is not True:
raise CMAPIBasicError('add_other_nodes_dbroots is only valid when read_only is true')

try:
add_node(
node, input_config_filename=config,
output_config_filename=config
output_config_filename=config,
read_only=read_only,
add_other_nodes_dbroots=add_other_nodes_dbroots,
)
if not get_dbroots(node, config):
add_dbroot(
host=node, input_config_filename=config,
output_config_filename=config
)
if not read_only: # Read-only nodes don't own dbroots
add_dbroot(
host=node, input_config_filename=config,
output_config_filename=config
)
except Exception as err:
raise CMAPIBasicError('Error while adding node.') from err

Expand Down Expand Up @@ -214,12 +231,16 @@ def remove_node(node: str, config: str = DEFAULT_MCS_CONF_PATH) -> dict:
node, input_config_filename=config,
output_config_filename=config
)

except Exception as err:
raise CMAPIBasicError('Error while removing node.') from err

response['node_id'] = node
active_nodes = get_active_nodes(config)
if len(active_nodes) > 0:
with NodeConfig().modify_config(config) as root:
update_dbroots_of_readonly_nodes(root)

update_revision_and_manager(
input_config_filename=config, output_config_filename=config
)
Expand Down
9 changes: 7 additions & 2 deletions cmapi/cmapi_server/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import socket
import time
from collections import namedtuple
from functools import partial
from random import random
from shutil import copyfile
from typing import Tuple, Optional
Expand Down Expand Up @@ -136,6 +135,10 @@ def start_transaction(
# this copy will be updated if an optional node can't be reached
real_active_nodes = set(active_nodes)
logging.trace(f'Active nodes on start transaction {active_nodes}')

if not len(active_nodes):
logging.warning('No active nodes found, transaction start will not have any effect')

for node in active_nodes:
url = f'https://{node}:8640/cmapi/{version}/node/begin'
node_success = False
Expand Down Expand Up @@ -379,7 +382,7 @@ async def update_config(node: str, headers: dict, body: dict) -> None:
) as response:
resp_json = await response.json(encoding='utf-8')
response.raise_for_status()
logging.info(f'Node {node} config put successfull.')
logging.info(f'Node {node} config put successful.')
except aiohttp.ClientResponseError as err:
# TODO: may be better to check if resp status is 422 cause
# it's like a signal that cmapi server raised it in
Expand Down Expand Up @@ -577,6 +580,7 @@ def get_dbroots(node, config=DEFAULT_MCS_CONF_PATH):
dbroots = []
smc_node = root.find('./SystemModuleConfig')
mod_count = int(smc_node.find('./ModuleCount3').text)

for i in range(1, mod_count+1):
ip_addr = smc_node.find(f'./ModuleIPAddr{i}-1-3').text
hostname = smc_node.find(f'./ModuleHostName{i}-1-3').text
Expand All @@ -596,6 +600,7 @@ def get_dbroots(node, config=DEFAULT_MCS_CONF_PATH):
dbroots.append(
smc_node.find(f"./ModuleDBRootID{i}-{j}-3").text
)

return dbroots


Expand Down
3 changes: 2 additions & 1 deletion cmapi/cmapi_server/logging_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ def enable_console_logging(logger: logging.Logger) -> None:
def config_cmapi_server_logging():
# add custom level TRACE only for develop purposes
# could be activated using API endpoints or cli tool without relaunching
add_logging_level('TRACE', 5)
if not hasattr(logging, 'TRACE'):
add_logging_level('TRACE', 5)
cherrypy._cplogging.LogManager.error = custom_cherrypy_error
# reconfigure cherrypy.access log message format
# Default access_log_format '{h} {l} {u} {t} "{r}" {s} {b} "{f}" "{a}"'
Expand Down
Loading