def test_get_first_key(): """Utils - Get First Key""" data = { 'path': 'ABC', 'details': { 'parent': { 'path': 'DEF', } }, 'empty_dict': {}, 'empty_list': [], 'events': [ { 'path': 'GHI' } ] } # 'path' is a top-level key and so should always be returned first assert_equal('ABC', utils.get_first_key(data, 'path')) # dicts and lists can be returned as well assert_equal(data['details'], utils.get_first_key(data, 'details')) # None is returned by default if no value is found assert_equal(None, utils.get_first_key(data, 'no-key-found')) # Custom default value is returned if specified assert_equal({}, utils.get_first_key(data, 'no-key-found', {}))
def can_merge(self, other): """Check if two alerts can be merged together. Args: other (Alert): Check if the instance can merge with this other alert. Returns: True if these alerts fit in the same merge window and have the same merge key values. """ if not self.merge_enabled or not other.merge_enabled: # Merge information is not defined for both of these alerts. return False older, newer = min(self, other), max(self, other) if newer.created > older.created + older.merge_window: # These alerts won't fit in a single merge window. return False if set(self.merge_by_keys) != set(other.merge_by_keys): # These alerts have different definitions of merge keys. return False return all( utils.get_first_key(self.record, key) == utils.get_first_key( other.record, key) for key in self.merge_by_keys)
def merge(cls, alerts): """Combine a list of alerts into a new merged alert. The caller is responsible for determining *which* alerts should be merged, this just implements the merge algorithm. Args: alerts (list): List of alerts to merge. These should all have the same values for their merge keys. Returns: Alert: A new alert whose record is formed by merging the records of all the alerts. The merged alert outputs are a union of all outputs in the original alerts. Other information (rule name, description, etc) is copied from the first alert. """ alerts = sorted(alerts) # Put alerts in chronological order. merge_keys = set(alerts[0].merge_by_keys) # Remove merge keys from the alert record, so that it doesn't show up in common/diff records = [ cls._clean_record(alert.record, merge_keys) for alert in alerts ] common = cls._compute_common(records) # Keys are named such that more important information is at the beginning alphabetically. new_record = { 'AlertCount': len(alerts), 'AlertTimeFirst': min(alert.created for alert in alerts).strftime(cls.DATETIME_FORMAT), 'AlertTimeLast': max(alert.created for alert in alerts).strftime(cls.DATETIME_FORMAT), 'MergedBy': { key: utils.get_first_key(alerts[0].record, key, '(n/a)') for key in merge_keys }, 'OtherCommonKeys': common, 'ValueDiffs': { alert.created.strftime(cls.DATETIME_FORMAT): cls._compute_diff(common, record) for alert, record in zip(alerts, records) } } # TODO: the cluster, log_source, source_entity, etc, could be different between alerts return cls( alerts[0].rule_name, new_record, alerts[-1].outputs, # Use the most recent set of outputs cluster=alerts[0].cluster, context=alerts[0].context, log_source=alerts[0].log_source, log_type=alerts[0].log_type, publishers=alerts[0].publishers, rule_description=alerts[0].rule_description, source_entity=alerts[0].source_entity, source_service=alerts[0].source_service, staged=any(alert.staged for alert in alerts))