66from copy import deepcopy
77from datetime import datetime
88from pathlib import Path
9+ from typing import Optional
910
1011import cherrypy
1112import pyotp
1213import requests
14+ from pydantic import BaseModel , Field , ValidationError , model_validator
15+
1316from mcs_node_control .models .dbrm import set_cluster_mode
1417from mcs_node_control .models .node_config import NodeConfig
1518from mcs_node_control .models .node_status import NodeStatus
16- from pydantic import ValidationError
17-
1819from cmapi_server .constants import (
1920 CMAPI_PACKAGE_NAME ,
2021 CMAPI_PORT ,
3031 SECRET_KEY ,
3132)
3233from cmapi_server .controllers .api_clients import NodeControllerClient
34+ from cmapi_server import helpers
3335from cmapi_server .controllers .error import APIError
3436from cmapi_server .exceptions import CMAPIBasicError , cmapi_error_to_422
37+ from cmapi_server .exceptions import validate_or_422 , exc_to_422
3538from cmapi_server .controllers .request_models import (
3639 ConfigPutRequestRootModel , StatefulConfigPutRequestModel ,
3740)
@@ -1997,12 +2000,13 @@ def put_stateful_config(self):
19972000 log_begin (module_logger , func_name )
19982001
19992002 request_body = cherrypy .request .json
2000- try :
2001- request_stateful_config = StatefulConfigModel .model_validate (
2002- request_body .get ('stateful_config_dict' )
2003- )
2004- except ValidationError as exp :
2005- raise_422_error (module_logger , func_name ,f'Invalid request body: { exp .errors ()} ' )
2003+ request_stateful_config = validate_or_422 (
2004+ StatefulConfigModel ,
2005+ request_body .get ('stateful_config_dict' ),
2006+ module_logger ,
2007+ func_name ,
2008+ prefix = 'Invalid request body' ,
2009+ )
20062010
20072011 success = AppStatefulConfig .apply_update (request_stateful_config )
20082012 if not success :
@@ -2014,3 +2018,52 @@ def put_stateful_config(self):
20142018 )
20152019
20162020 return {'timestamp' : str (datetime .now ()), 'success' : success }
2021+
2022+
2023+ class CmapiConfigPatchModel (BaseModel ):
2024+ failover_sampling_interval_seconds : Optional [int ] = Field (default = None , ge = 1 )
2025+
2026+ @model_validator (mode = 'after' )
2027+ def ensure_any_present (self ):
2028+ if self .failover_sampling_interval_seconds is None :
2029+ raise ValueError ('At least one field must be provided' )
2030+ return self
2031+
2032+
2033+ class CmapiConfigController :
2034+ @cherrypy .tools .timeit ()
2035+ @cherrypy .tools .json_in ()
2036+ @cherrypy .tools .json_out ()
2037+ @cherrypy .tools .validate_api_key () # pylint: disable=no-member
2038+ def patch_cmapi_config (self ):
2039+ """Update our own CMAPI config section in Columnstore.xml"""
2040+ func_name = 'patch_cmapi_config'
2041+ log_begin (module_logger , func_name )
2042+
2043+ req_model = validate_or_422 (
2044+ CmapiConfigPatchModel ,
2045+ cherrypy .request .json ,
2046+ module_logger ,
2047+ func_name ,
2048+ prefix = 'Invalid payload' ,
2049+ )
2050+
2051+ # Update Columnstore.xml under <CMAPIConfig>
2052+ nc = NodeConfig ()
2053+ with nc .modify_config (DEFAULT_MCS_CONF_PATH ) as root :
2054+ cmapi_node = helpers .get_or_create_child_xml_node (root , 'CMAPIConfig' )
2055+
2056+ # Failover sampling interval
2057+ if req_model .failover_sampling_interval_seconds is not None :
2058+ node = helpers .get_or_create_child_xml_node (cmapi_node , 'FailoverSamplingIntervalSeconds' )
2059+ node .text = str (req_model .failover_sampling_interval_seconds )
2060+
2061+ with exc_to_422 (module_logger , func_name , prefix = 'Failed to bump config revision' ):
2062+ helpers .update_revision_and_manager (input_config_filename = DEFAULT_MCS_CONF_PATH )
2063+
2064+ # Broadcast updated config
2065+ with cmapi_error_to_422 (module_logger , func_name ):
2066+ with TransactionManager () as txn :
2067+ helpers .broadcast_new_config (nodes = txn .success_txn_nodes )
2068+
2069+ return {'timestamp' : str (datetime .now ())}
0 commit comments