def update_or_patch_alarm_definition(self, tenant_id, alarm_definition_id, name, expression, sub_expr_list, actions_enabled, description, alarm_actions, ok_actions, undetermined_actions, match_by, severity, patch=False): with self._db_engine.begin() as conn: original_row = self._get_alarm_definition(conn, tenant_id, alarm_definition_id) rows = self._get_sub_alarm_definitions(conn, alarm_definition_id) old_sub_alarm_defs_by_id = {} for row in rows: sad = sub_alarm_definition.SubAlarmDefinition(row=row) old_sub_alarm_defs_by_id[sad.id] = sad if expression: (changed_sub_alarm_defs_by_id, new_sub_alarm_defs_by_id, old_sub_alarm_defs_by_id, unchanged_sub_alarm_defs_by_id ) = self._determine_sub_expr_changes( alarm_definition_id, old_sub_alarm_defs_by_id, sub_expr_list) if old_sub_alarm_defs_by_id or new_sub_alarm_defs_by_id: new_count = (len(new_sub_alarm_defs_by_id) + len(changed_sub_alarm_defs_by_id) + len(unchanged_sub_alarm_defs_by_id)) old_count = len(old_sub_alarm_defs_by_id) if new_count != old_count: msg = 'number of subexpressions must not change' else: msg = 'metrics in subexpression must not change' raise exceptions.InvalidUpdateException(msg.encode('utf8')) else: unchanged_sub_alarm_defs_by_id = old_sub_alarm_defs_by_id changed_sub_alarm_defs_by_id = {} new_sub_alarm_defs_by_id = {} old_sub_alarm_defs_by_id = {} # Get a common update time now = datetime.datetime.utcnow() if name is None: new_name = original_row['name'] else: new_name = name.encode('utf8') if description is None: if patch: new_description = original_row['description'] else: new_description = '' else: new_description = description.encode('utf8') if expression is None: new_expression = original_row['expression'] else: new_expression = expression.encode('utf8') if severity is None: if patch: new_severity = original_row['severity'] else: new_severity = 'LOW' else: new_severity = severity.encode('utf8') if match_by is None: if patch: new_match_by = original_row['match_by'] else: new_match_by = None else: new_match_by = ",".join(match_by).encode('utf8') if new_match_by != original_row['match_by']: msg = "match_by must not change".encode('utf8') raise exceptions.InvalidUpdateException(msg) if actions_enabled is None: new_actions_enabled = original_row['actions_enabled'] else: new_actions_enabled = actions_enabled conn.execute( self.update_or_patch_alarm_definition_update_ad_query.values( name=bindparam('b_name'), description=bindparam('b_description'), expression=bindparam('b_expression'), match_by=bindparam('b_match_by'), severity=bindparam('b_severity'), actions_enabled=bindparam('b_actions_enabled'), updated_at=bindparam('b_updated_at')), b_name=new_name, b_description=new_description, b_expression=new_expression, b_match_by=new_match_by, b_severity=new_severity, b_actions_enabled=bool(new_actions_enabled), b_updated_at=now, b_tenant_id=tenant_id, b_id=alarm_definition_id) parms = [] for sub_alarm_def_id in old_sub_alarm_defs_by_id.values(): parms.append({'b_id': sub_alarm_def_id.id}) if len(parms) > 0: query = self.update_or_patch_alarm_definition_delete_sad_query conn.execute(query, parms) parms = [] for sub_alarm_definition_id, sub_alarm_def in ( changed_sub_alarm_defs_by_id.iteritems()): parms.append({ 'b_operator': sub_alarm_def.operator, 'b_threshold': sub_alarm_def.threshold, 'b_updated_at': now, 'b_id': sub_alarm_definition_id }) if len(parms) > 0: query = self.update_or_patch_alarm_definition_update_sad_query conn.execute(query, parms) parms = [] parms_sadd = [] for sub_alarm_def in new_sub_alarm_defs_by_id.values(): adi = sub_alarm_def.alarm_definition_id function = sub_alarm_def.function.encode('utf8') metric_name = sub_alarm_def.metric_name.encode('utf8') operator = sub_alarm_def.operator.encode('utf8') threshold = str(sub_alarm_def.threshold).encode('utf8') period = str(sub_alarm_def.period).encode('utf8') periods = str(sub_alarm_def.periods).encode('utf8') parms.append({ 'b_id': sub_alarm_def.id, 'b_alarm_definition_id': adi, 'b_function': function, 'b_metric_name': metric_name, 'b_operator': operator, 'b_threshold': threshold, 'b_period': period, 'b_periods': periods, 'b_created_at': now, 'b_updated_at': now }) for name, value in sub_alarm_def.dimensions.items(): sadi = sub_alarm_def.id parms_sadd.append({ 'b_sub_alarm_definition_id': sadi, 'b_dimension_name': name.encode('utf8'), 'b_value': value.encode('utf8') }) if len(parms) > 0: query = self.update_or_patch_alarm_definition_insert_sad_query conn.execute(query, parms) if len(parms_sadd) > 0: query = self.update_or_patch_alarm_definition_insert_sadd_query conn.execute(query, parms_sadd) # Delete old alarm actions if patch: if alarm_actions is not None: self._delete_alarm_actions(conn, alarm_definition_id, 'ALARM') if ok_actions is not None: self._delete_alarm_actions(conn, alarm_definition_id, 'OK') if undetermined_actions is not None: self._delete_alarm_actions(conn, alarm_definition_id, 'UNDETERMINED') else: conn.execute(self.delete_aa_query, b_alarm_definition_id=alarm_definition_id) # Insert new alarm actions self._insert_into_alarm_action(conn, alarm_definition_id, alarm_actions, u"ALARM") self._insert_into_alarm_action(conn, alarm_definition_id, undetermined_actions, u"UNDETERMINED") self._insert_into_alarm_action(conn, alarm_definition_id, ok_actions, u"OK") ad = self.ad_s query = (self.base_query.select_from(self.base_query_from).where( ad.c.tenant_id == bindparam('b_tenant_id')).where( ad.c.id == bindparam('b_id')).where( ad.c.deleted_at == null())) updated_row = conn.execute(query, b_id=alarm_definition_id, b_tenant_id=tenant_id).fetchone() if updated_row is None: raise Exception("Failed to find current alarm definition") sub_alarm_defs_dict = { 'old': old_sub_alarm_defs_by_id, 'changed': changed_sub_alarm_defs_by_id, 'new': new_sub_alarm_defs_by_id, 'unchanged': unchanged_sub_alarm_defs_by_id } # Return the alarm def and the sub alarm defs return updated_row, sub_alarm_defs_dict
def test_alarm_definition_update_missing_fields(self): self.alarm_def_repo_mock.return_value.get_alarm_definitions.return_value = [] self.alarm_def_repo_mock.return_value.update_or_patch_alarm_definition.return_value = ( { u'alarm_actions': [], u'ok_actions': [], u'description': u'Non-ASCII character: \u2603'.encode('utf-8'), u'match_by': u'hostname', u'name': u'Test Alarm', u'actions_enabled': True, u'undetermined_actions': [], u'expression': u'max(test.metric{hostname=host}) gte 1', u'id': u'00000001-0001-0001-0001-000000000001', u'is_deterministic': False, u'severity': u'LOW' }, { 'old': { '11111': sub_alarm_definition.SubAlarmDefinition( row={ 'id': '11111', 'alarm_definition_id': u'00000001-0001-0001-0001-000000000001', 'function': 'max', 'metric_name': 'test.metric', 'dimensions': 'hostname=host', 'operator': 'gte', 'threshold': 1, 'period': 60, 'periods': 1, 'is_deterministic': False }) }, 'changed': {}, 'new': {}, 'unchanged': { '11111': sub_alarm_definition.SubAlarmDefinition( row={ 'id': '11111', 'alarm_definition_id': u'00000001-0001-0001-0001-000000000001', 'function': 'max', 'metric_name': 'test.metric', 'dimensions': 'hostname=host', 'operator': 'gte', 'threshold': 1, 'period': 60, 'periods': 1, 'is_deterministic': False }) } }) expected_def = { u'id': u'00000001-0001-0001-0001-000000000001', u'alarm_actions': [], u'ok_actions': [], u'description': u'Non-ASCII character: \u2603', u'links': [{ u'href': u'http://falconframework.org/v2.0/alarm-definitions/' u'00000001-0001-0001-0001-000000000001/00000001-0001-0001-0001-000000000001', u'rel': u'self' }], u'match_by': [u'hostname'], u'name': u'Test Alarm', u'actions_enabled': True, u'undetermined_actions': [], u'expression': u'max(test.metric{hostname=host}) gte 1', u'severity': u'LOW', u'deterministic': False } alarm_def = { u'alarm_actions': [], u'ok_actions': [], u'description': u'', u'match_by': [u'hostname'], u'name': u'Test Alarm', u'actions_enabled': True, u'undetermined_actions': [], u'expression': u'max(test.metric{hostname=host}) gte 1', u'severity': u'LOW' } result = self.simulate_request("/v2.0/alarm-definitions/%s" % expected_def[u'id'], headers={ 'X-Roles': 'admin', 'X-Tenant-Id': TENANT_ID }, method="PUT", body=json.dumps(alarm_def)) self.assertEqual(self.srmock.status, falcon.HTTP_200) result_def = json.loads(result[0]) self.assertEqual(result_def, expected_def) for key, value in alarm_def.iteritems(): del alarm_def[key] self.simulate_request("/v2.0/alarm-definitions/%s" % expected_def[u'id'], headers={ 'X-Roles': 'admin', 'X-Tenant-Id': TENANT_ID }, method="PUT", body=json.dumps(alarm_def)) self.assertEqual(self.srmock.status, "422 Unprocessable Entity", u"should have failed without key {}".format(key)) alarm_def[key] = value
def test_alarm_definition_patch(self): self.alarm_def_repo_mock.return_value.get_alarm_definitions.return_value = [] description = u'Non-ASCII character: \u2603' new_name = u'Test Alarm Updated' actions_enabled = True alarm_def_id = u'00000001-0001-0001-0001-000000000001' alarm_expression = u'max(test.metric{hostname=host}) gte 1' severity = u'LOW' match_by = u'hostname' self.alarm_def_repo_mock.return_value.update_or_patch_alarm_definition.return_value = ( { u'alarm_actions': [], u'ok_actions': [], u'description': description, u'match_by': match_by, u'name': new_name, u'actions_enabled': actions_enabled, u'undetermined_actions': [], u'is_deterministic': False, u'expression': alarm_expression, u'id': alarm_def_id, u'severity': severity }, { 'old': { '11111': sub_alarm_definition.SubAlarmDefinition( row={ 'id': '11111', 'alarm_definition_id': u'00000001-0001-0001-0001-000000000001', 'function': 'max', 'metric_name': 'test.metric', 'dimensions': 'hostname=host', 'operator': 'gte', 'threshold': 1, 'period': 60, 'is_deterministic': False, 'periods': 1 }) }, 'changed': {}, 'new': {}, 'unchanged': { '11111': sub_alarm_definition.SubAlarmDefinition( row={ 'id': '11111', 'alarm_definition_id': u'00000001-0001-0001-0001-000000000001', 'function': 'max', 'metric_name': 'test.metric', 'dimensions': 'hostname=host', 'operator': 'gte', 'threshold': 1, 'period': 60, 'is_deterministic': False, 'periods': 1 }) } }) expected_def = { u'id': alarm_def_id, u'alarm_actions': [], u'ok_actions': [], u'description': description, u'links': [{ u'href': u'http://falconframework.org/v2.0/alarm-definitions/' u'00000001-0001-0001-0001-000000000001/00000001-0001-0001-0001-000000000001', u'rel': u'self' }], u'match_by': [match_by], u'name': new_name, u'actions_enabled': actions_enabled, u'undetermined_actions': [], u'deterministic': False, u'expression': alarm_expression, u'severity': severity, } alarm_def = { u'name': u'Test Alarm Updated', } result = self.simulate_request("/v2.0/alarm-definitions/%s" % expected_def[u'id'], headers={ 'X-Roles': 'admin', 'X-Tenant-Id': TENANT_ID }, method="PATCH", body=json.dumps(alarm_def)) self.assertEqual(self.srmock.status, falcon.HTTP_200) result_def = json.loads(result[0]) self.assertEqual(result_def, expected_def) # If the alarm-definition-updated event does not have all of the # fields set, the Threshold Engine will get confused. For example, # if alarmActionsEnabled is none, thresh will read that as false # and pass that value onto the Notification Engine which will not # create a notification even actions_enabled is True in the # database. So, ensure all fields are set correctly ((_, event), _) = self._send_event.call_args expr = u'max(test.metric{hostname=host}, 60) gte 1 times 1' sub_expression = { '11111': { u'expression': expr, u'function': 'max', u'metricDefinition': { u'dimensions': { u'uname': 'host' }, u'name': 'test.metric' }, u'operator': 'gte', u'period': 60, u'periods': 1, u'threshold': 1 } } fields = { u'alarmActionsEnabled': actions_enabled, u'alarmDefinitionId': alarm_def_id, u'alarmDescription': description, u'alarmExpression': alarm_expression, u'alarmName': new_name, u'changedSubExpressions': {}, u'matchBy': [match_by], u'severity': severity, u'tenantId': u'fedcba9876543210fedcba9876543210', u'newAlarmSubExpressions': {}, u'oldAlarmSubExpressions': sub_expression, u'unchangedSubExpressions': sub_expression } reference = {u'alarm-definition-updated': fields} self.assertEqual(reference, event)