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.

222 lines
10 KiB

# Copyright (C) 2005-2024 Splunk Inc. All Rights Reserved.
import ITOA.itoa_common as utils
from ITOA.controller_utils import ITOAError
from ITOA.itoa_factory import instantiate_object
from ITOA.setup_logging import getLogger
from itsi.upgrade.timezones import get_new_time_blocks, _is_maintenance_end_time_indefinite, \
apply_timezone_offset, apply_timezone_offset_for_kpi_threshold_template
logger = getLogger(logger_name="ITOA.itoa_shift_time_offset")
logger.debug("Initialized itoa shift time offset handler log")
MAX_TIME_OFFSET_IN_MINUTES = 1440
def construct_filter_for_linked_thr_tmp(thr_tmp_object_ids):
"""
Create a filter for bulk filtering services containing specified kpi threshold template IDs
@type: list
@param thr_tmp_object_ids: list of object IDs of kpi threshold template to filter
@rtype dict | None
@return Filter object for services
"""
if len(thr_tmp_object_ids) >= 1 and len(thr_tmp_object_ids[0]) > 0:
get_bulk_filter = {'$or': []}
for thr_tmp_object_id in thr_tmp_object_ids:
get_bulk_filter['$or'].append({'kpis.kpi_threshold_template_id': thr_tmp_object_id})
return get_bulk_filter
else:
return None
def construct_filter_for_object_keys(object_keys_list):
"""
Create a filter for bulk filtering objects containing specified object keys
@type: list
@param object_keys_list: list of object IDs of specific object to filter
@rtype: dict
@return: Filter object for specific object type
"""
if len(object_keys_list) >= 1 and len(object_keys_list[0]) > 0:
get_bulk_filter = {'$or': []}
for object_key in object_keys_list:
get_bulk_filter['$or'].append({'_key': object_key})
return get_bulk_filter
else:
return {}
def apply_timezone_offset_for_service(service, offset_in_min, change_linked=False, threshold_template_ids=[]):
"""
Given the number of hours needed to offset time_blocks, apply the change to unlinked KPIs in a service
if change_linked is false, else update only KPIs linked with threshold template
@type service: object
@param service: Service object instance to update
@type offset_in_min: int
@param offset_in_min: the # of hours of offset to apply
@type change_linked: boolean
@param change_linked: if True change only KPIs with linked threshold template
@type threshold_template_ids: list
@param threshold_template_ids: List of threshold template IDs whose linked KPIs to update
@rtype: None
@return: None although service input reference will be updated
"""
if not (MAX_TIME_OFFSET_IN_MINUTES * -1 <= offset_in_min <= MAX_TIME_OFFSET_IN_MINUTES):
message = 'Timezone offset specified is invalid. Must be within a 24-hour (1440 minute) range. Specified value: %s ' % offset_in_min
logger.error(message)
raise ITOAError(status=400, message=message)
for kpi in service.get('kpis', []):
if not isinstance(kpi, dict):
continue
if kpi.get('_key').startswith('SHKPI'):
logger.debug('Skipping ServiceHealthScore kpi for service: %s', service.get('title'))
continue
if kpi.get('time_variate_thresholds') is False:
logger.debug('Skipping KPI %s as time policy is not enabled.', kpi.get('title'))
continue
linked_thr_tmp_id = kpi.get('kpi_threshold_template_id')
if change_linked:
if linked_thr_tmp_id == '' or linked_thr_tmp_id is None or linked_thr_tmp_id == 'custom' or linked_thr_tmp_id not in threshold_template_ids:
# Skipping the KPIs which are not linked with specific Threshold templates
logger.debug('Skipping the KPI "%s" which is not linked with any KPI threshold template', kpi.get('title'))
continue
else:
if linked_thr_tmp_id is not None and linked_thr_tmp_id != '' and linked_thr_tmp_id != 'custom':
# Skipping the KPIs which are linked with specific Threshold templates
logger.debug('Skipping the KPI "%s" which are already linked with KPI threshold template %s', kpi.get('title'), linked_thr_tmp_id)
continue
policy_spec = kpi.get('time_variate_thresholds_specification')
if not isinstance(policy_spec, dict):
continue
policies = policy_spec.get('policies')
if not isinstance(policies, dict):
continue
for policy_key, policy in policies.items():
if not isinstance(policy, dict):
continue
time_blocks = policy.get('time_blocks')
if not isinstance(time_blocks, list):
continue
kpi['time_variate_thresholds_specification']['policies'][policy_key]['time_blocks'] = get_new_time_blocks(time_blocks, offset_in_min)
class ShiftTimeOffsetUtils(object):
"""
Class for Utility of shift time offset functions
"""
def __init__(self, session_key, owner):
self._session_key = session_key
self.owner = owner
def get_objects_to_update(self, object_type, payload_data):
"""
Helper function to get bulk objects for object type based on
the filtering of payload data
@type: string
@param object_type: the ITOA object type
@type: dict
@param payload_data: data passed in the endpoint to apply time offset to
@rtype: dict
@return: Instance Object, Keys of Objects fetched, Objects fetched upon filtering
"""
if '_keys' not in payload_data.get(object_type):
message = '_keys is a mandatory attrbute.'
logger.error(message)
raise ITOAError(status=400, message=message)
object_keys = utils.get_object(payload_data.get(object_type).get('_keys'))
object_key_filter = construct_filter_for_object_keys(object_keys)
try:
object_type_instance = instantiate_object(self._session_key, self.owner, object_type)
except Exception as e:
logger.exception(e)
raise ITOAError(status=400, message=e)
return object_type_instance, object_keys, object_type_instance.get_bulk(self.owner, filter_data=object_key_filter)
def update_object_time_policy(self, data, offset):
"""
Function to handle all object types in data from shift_time_offset endpoint
and change the time blocks for specified object keys
@type data: dict
@param data: payload data from the endpoint argument
@type offset: int
@param offset: offset in seconds to shift time policy with
@rtype: dict
@return: Success message or ITOA Error(if any)
"""
if 'service' in data:
object_type_instance, service_object_keys, objects = self.get_objects_to_update('service', data)
if len(objects) < 1:
logger.debug('No service objects matched request')
else:
for json_data in objects:
apply_timezone_offset_for_service(json_data, int(offset / 60))
# Here, passing skip_kpi_consistancy_check True, and ignore_refresh_impacted_objects True as
# there is no need to perform additional validations on objects that are already created
object_type_instance.save_batch(self.owner, objects, validate_names=False, ignore_refresh_impacted_objects=True, skip_kpi_consistancy_check=True)
if 'kpi_threshold_template' in data:
object_type_instance, thr_tmp_object_keys, objects = self.get_objects_to_update('kpi_threshold_template', data)
if len(objects) < 1:
logger.debug('No kpi threshold template objects matched request')
else:
for json_data in objects:
apply_timezone_offset_for_kpi_threshold_template(json_data, int(offset / 60))
object_type_instance.save_batch(self.owner, objects, False)
# Now get all linked KPI services
if len(thr_tmp_object_keys) == 0:
thr_tmp_object_keys = [data.get('_key') for data in objects]
linked_thr_tmp_filter = construct_filter_for_linked_thr_tmp(thr_tmp_object_keys)
if linked_thr_tmp_filter is not None:
try:
object_type_instance = instantiate_object(self._session_key, self.owner, 'service')
except Exception as e:
logger.exception(e)
raise ITOAError(status=400, message=e)
service_objects = object_type_instance.get_bulk(self.owner, filter_data=linked_thr_tmp_filter)
if len(service_objects) > 0:
for json_data in service_objects:
apply_timezone_offset_for_service(json_data, int(offset / 60), True, thr_tmp_object_keys)
# Here, passing skip_kpi_consistancy_check True, and ignore_refresh_impacted_objects True as
# there is no need to perform additional validations on objects that are already created
object_type_instance.save_batch(self.owner, service_objects, validate_names=False, ignore_refresh_impacted_objects=True, skip_kpi_consistancy_check=True)
else:
logger.debug("No Service Kpis are linked to Specified KPI threshold template " + str([data.get('_key') for data in objects]))
if 'maintenance_calendar' in data:
object_type_instance, mtw_object_keys, mw_objects = self.get_objects_to_update('maintenance_calendar', data)
if len(mw_objects) < 1:
logger.debug('No maintenance calendar objects matched request')
else:
for json_data in mw_objects:
for json_field in ['start_time', 'end_time']:
# Only convert offset if end_time is not set to "Indefinite" for maintenance calendars
if json_field == 'end_time' and _is_maintenance_end_time_indefinite(json_data):
continue
apply_timezone_offset(json_data, json_field, offset)
object_type_instance.save_batch(self.owner, mw_objects, False)
return {"message": "success"}