You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

216 lines
9.5 KiB

# Copyright (C) 2005-2025 Splunk Inc. All Rights Reserved.
import json
import re
import splunk.rest as rest
from ITOA.itoa_common import get_conf_data, normalize_bool_flag, convert_to_bytes
from ITOA.saved_search_utility import SavedSearch
from itsi.upgrade.constants import CONF_COLLECTIONS, CONF_TRANSFORMS, OP_SET_EQUAL, OP_BOOL_EQUAL, OP_GREATER_EQUAL, \
OP_EQUAL, EXPECTED_COLLECTIONS, COLLECTION_STATS_URL, KVSTORE_COLLECTION_SIZE_LIMIT_DEFAULT, CONF_COMMANDS, \
CONF_SAVEDSEARCHES
from migration.migration_precheck import MigrationPreCheck
class EAPrechecks(MigrationPreCheck):
def __init__(self, session_key, logger, pre_checks, skip_pre_checks):
MigrationPreCheck.__init__(self, session_key, logger, pre_checks, skip_pre_checks)
def precheck_RECHK(self):
'''
check if rules engine is enabled/disabled
and populate precheck_results list accordingly
@rtype: list
@return: list of dictionaries that has following key/values:
{bool} if the precheck passed
{string} corresponding recommendation
'''
self.rules_engine_saved_search = SavedSearch.get_search(
self.session_key, 'itsi_event_grouping')
if int(self.rules_engine_saved_search['disabled']) == 1:
return [self._make_result(False, 'Rules Engine is not running', 'Warn')]
return [self._make_result(True, 'Rules Engine is up and running', 'Success' )]
def precheck_KVSIZECHK(self):
'''
check if kv store size checks limits are not exceeded
and populate precheck_results list accordingly
@rtype: list
@return: list of dictionaries that has following key/values:
{bool} if the precheck passed
{string} corresponding recommendation
'''
results = self.get_collection_sizes()
if isinstance(results, tuple):
return [self._make_result(results[0], results[1], 'Error')]
kv_store_collection_size_limit = KVSTORE_COLLECTION_SIZE_LIMIT_DEFAULT
stanzas = get_conf_data(self.session_key, 'itsi_event_management', app='SA-ITOA')
if 'precheck' in stanzas and 'kv_store_collection_size_limit' in stanzas['precheck']:
kv_store_collection_size_limit = int(stanzas['precheck']['kv_store_collection_size_limit'])
self.logger.info('Got kv_store_collection_size_limit=%s from itsi_event_management.conf'
% kv_store_collection_size_limit)
ret_list = []
collection_list = []
for each_result in results:
self.logger.info('Collection %s for app %s has %s objects.' %
(each_result['collection'], each_result['app'], each_result['count']))
if int(each_result['count']) >= kv_store_collection_size_limit:
ret_list.append(self._make_result(
False, 'The %s collection contains %s objects which exceeds the KV store size limit of %s objects.' %
(each_result['collection'], each_result['count'], kv_store_collection_size_limit), 'Warn'))
if each_result['collection'] in EXPECTED_COLLECTIONS:
collection_list.append(each_result['collection'])
if len(collection_list) < len(EXPECTED_COLLECTIONS):
ret_list.append('Some KVStore collection(s) are missing: %s' %
filter(lambda x: x not in EXPECTED_COLLECTIONS, collection_list))
if len(ret_list) == 0:
return [self._make_result(True, 'KV store size checks are within limits', 'Success')]
return ret_list
def get_collection_sizes(self):
'''
Get collection sizes
@rtype: list or tuple
@return: list of collections and sizes in the system if success, otherwise return tuple for any failures.
'''
try:
response, content = rest.simpleRequest(COLLECTION_STATS_URL, sessionKey=self.session_key, method='GET',
getargs={'output_mode': 'json'}, rawResult=True)
except Exception as e:
self.logger.exception(e)
return False, "Failed to fetch KV store statistics."
if response.status != 200:
message = 'Error retrieving KV store statistics. Response: %s, Content: %s' % (response, content)
self.logger.error(message)
return False, message
data = json.loads(content)
if 'entry' not in data or len(data['entry']) < 1:
message = 'Cannot find entry in the collection statistics.'
self.logger.error(message)
return False, message
ret_list = []
itsi_collection = ['SA-ITOA', 'SA-ITSI-ATAD', 'SA-ITSI-MetricAD', 'SA-UserAccess']
try:
for d in data['entry'][0]['content']['data']:
item = json.loads(d)
ns = item['ns'].split('.')
if ns[0] in itsi_collection:
ret_list.append({
'app': ns[0],
'collection': ns[1],
'count': int(item['count'])
})
except Exception as e:
message = 'Cannot load collection entry from response: %s' % e.message
self.logger.error(message)
return False, message
return ret_list
def precheck_EACONFIG(self):
'''
check if various EA config checks limits are not exceeded
and populate precheck_results list accordingly
@rtype: list
@return: list of dictionaries that has following key/values:
{bool} if the precheck passed
{string} corresponding recommendation
'''
ret_list = self.check_config('collections', CONF_COLLECTIONS)
ret_list.extend(self.check_config('transforms', CONF_TRANSFORMS))
ret_list.extend(self.check_config('commands', CONF_COMMANDS))
ret_list.extend(self.check_config('savedsearches', CONF_SAVEDSEARCHES))
if len(ret_list) == 0:
return [self._make_result(True, 'Event Analytics configurations are expected', 'Success')]
return ret_list
def criteria_to_string(self, criteria):
'''
Convert criteria to human readable string
@type criteria: dict
@param criteria: the criteria to be converted
@rtype: basestring
@return: the human readable string
'''
if criteria['op'] == OP_SET_EQUAL:
return 'Expected values in the list are %s' % ', '.join(['"%s"' % s for s in criteria['value']])
return 'Expected value is "%s"' % criteria['value']
def check_config(self, conf_file, stanza_config, app='SA-ITOA'):
'''
Check whether configuration in a config file satisfy the criteria
@type conf_file: basestring
@param conf_file: The config file name without .conf extention
@type stanza_config: dict
@param stanza_config: the configuration of the criterias for the stanzas to be checked.
@type app: basestring
@param app: the app context
@rtype: list of dict
@return: the dict of the results
'''
ret_list = []
stanza_dict = get_conf_data(self.session_key, conf_file, app=app)
for stanza in stanza_dict:
if stanza not in stanza_config:
continue
params = stanza_dict.get(stanza)
for param, check in stanza_config[stanza].items():
if param not in params:
self.logger.error('Cannot find param %s in stanza %s in file %s' % (param, stanza, conf_file))
continue
value = params.get(param)
# extract number from a string if conf file is commands.conf
# the expected string format is command.arg.1=-J-Xmx8192M
tmp = re.search(r'[A-Za-z]*(\d+[KMGTkmgt])', value)
if tmp:
value = tmp.group(1)
value = convert_to_bytes(value)
self.logger.info('Verifying parameter %s with criteria %s on value %s (config file: %s, stanza: %s)'
% (param, check, value, conf_file, stanza))
result = self.verify_value(check, value)
if not result:
message = 'Validation failed for parameter in %s.conf: \'%s\' = "%s" in the [%s] stanza. %s' %\
(conf_file, param, value, stanza, self.criteria_to_string(check))
self.logger.warn(message)
ret_list.append(self._make_result(False, message, 'Warn'))
return ret_list
def verify_value(self, check_criteria, actual_value):
"""
Verify if the actual value match the check criteria
@type check_criteria: dict
@param check_criteria: the check criteria to pass
@type actual_value: basestring
@param actual_value: the actual value in string type
@rtype: bool
@return: True means pass, False means failed.
"""
if check_criteria['op'] == OP_EQUAL:
return actual_value == check_criteria['value']
if check_criteria['op'] == OP_SET_EQUAL:
return set([v.strip() for v in actual_value.split(',')]) == set(check_criteria['value'])
if check_criteria['op'] == OP_GREATER_EQUAL:
return actual_value >= check_criteria['value']
if check_criteria['op'] == OP_BOOL_EQUAL:
return normalize_bool_flag(actual_value) == normalize_bool_flag(check_criteria['value'])