(Non)deterministic support for alarms

The deterministic alarm's main trait
is preventing from transition to UNDETERMINTED state.
That allows effectively create alarms for metrics
which behaviour is similar to events (i.e. generated
upon critical situation in the system). By default (non-deterministic)
alarms created for those metrics would stay in UNDETERMINED stay
for most of the time.

Implements: blueprint alarmsonlogs
Change-Id: I506fcd0b9861e902a2bfcfa768d402c568b85cea
This commit is contained in:
Tomasz Trębski 2016-04-08 12:54:38 +02:00
parent 5ce5074a38
commit 0cce983d95
22 changed files with 525 additions and 47 deletions

View File

@ -3,6 +3,7 @@ Angelo Mendonca <angelomendonca@gmail.com>
Ben Motz <bmotz@cray.com>
Brad Klein <bradley.klein@twcable.com>
Craig Bryant <craig.bryant@hp.com>
David C Kennedy <david.c.kennedy@hp.com>
Deklan Dieterly <deklan.dieterly@hp.com>
Deklan Dieterly <deklan.dieterly@hpe.com>
Deklan Dieterly <dieterly@gmail.com>
@ -38,6 +39,7 @@ Tomasz Trębski <tomasz.trebski@ts.fujitsu.com>
Tong Li <litong01@us.ibm.com>
Victor Ion Munteanu <victor.munteanu@equillon.ro>
Witold Bedyk <witold.bedyk@est.fujitsu.com>
ZhiQiang Fan <aji.zqfan@gmail.com>
alpineriveredge <alpineriveredge@gmail.com>
bklei <bradley.klein@twcable.com>
cindy oneill <cindy.o-neill@hp.com>

View File

@ -1,5 +1,6 @@
/*
* (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP
* Copyright 2016 FUJITSU LIMITED
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -156,6 +157,7 @@ CREATE TABLE `sub_alarm_definition` (
`threshold` double NOT NULL,
`period` int(11) NOT NULL,
`periods` int(11) NOT NULL,
`is_deterministic` tinyint(1) NOT NULL DEFAULT '0',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`),

View File

@ -481,6 +481,58 @@ The Alarms are evaluated and their state is set once per minute.
Alarms contain three fields that may be edited via the API. These are the alarm state, lifecycle state, and the link. The alarm state is updated by Monasca as measurements are evaluated, and can be changed manually as necessary. The lifecycle state and link fields are not maintained or updated by Monasca, instead these are provided for storing information related to external tools.
### Deterministic or non-deterministic alarms
By default all alarm definitions are assumed to be **non-deterministic**.
There are 3 possible states such alarms can transition to: *OK*, *ALARM*,
*UNDETERMINED*. On the other hand, alarm definitions can be also
**deterministic**. In that case alarm is allowed to transition only: *OK*
and *ALARM* state.
Following expression ```avg(cpu.user_perc{hostname=compute_node_1}) > 10``` means that potential
alarm and transition to *ALARM* state is restricted to specific machine. If for some reason that
host would crash and stay offline long enough, there would be no measurements received from it.
In this case alarm will transition to *UNDETERMINED* state.
On the other hand, some metrics are irregular and look more like events. One case is
metric created only if something critical happens in the system.
For example an error in log file or deadlock in database.
If non-deterministic alarm definition would be created using expression ```count(log.error{component=mysql}) >= 1)```,
that alarm could stay in *UNDETERMINED* state for most of its lifetime.
However, from operator point of view, if there are no errors related to MySQL, everything works correctly.
Answer to that situation is creating *deterministic* alarm definition
using expression ```count(log.error{component=mysql}, deterministic) >= 1```.
The deterministic alarm's main trait is preventing from transition to *UNDETERMINED* state.
The alarm should be *OK* if no data is received. Also such alarms transition to *OK* immediately when created,
rather than to *UNDETERMINED* state.
Finally, it must be mentioned that alarm definition can be composed of multiple expressions and
that *deterministic* is actually part of it. The entire alarm definition is considered *deterministic*
only if all of its expressions are such. Otherwise the alarm is *non-deterministic*.
For example:
```
avg(disk.space_used_perc{hostname=compute_node_1}) >= 99
and
count(log.error{hostname=compute_node_1,component=kafka},deterministic) >= 1
```
potential alarm will transition to *ALARM* state if there is no usable disk space left and kafka starts to report errors regarding
inability to save data to it. Second expression is *deterministic*, however entire alarm will be kept in *UNDETERMINED* state
until such situation happens.
On the other hand, expression like this:
```
avg(disk.space_used_perc{hostname=compute_node_1},deterministic) >= 99
and
count(log.error{hostname=compute_node_1,component=kafka},deterministic) >= 1
```
makes entire alarm *deterministic*. In other words - *all parts of alarm's expression
must be marked as deterministic in order for entire alarm to be considered such*.
Having definition like one above, potential alarm will stay in *OK* state as long as there is enough
disk space left at *compute_node_1* and there are no errors reported from *kafka* running
at the same host.
## Alarm Definition Expressions
The alarm definition expression syntax allows the creation of simple or complex alarm definitions to handle a wide variety of needs. Alarm expressions are evaluated every 60 seconds.
@ -511,11 +563,16 @@ Each subexpression is made up of several parts with a couple of options:
````
<sub_expression>
::= <function> '(' <metric> [',' period] ')' <relational_operator> threshold_value ['times' periods]
::= <function> '(' <metric> [',' deterministic] [',' period] ')' <relational_operator> threshold_value ['times' periods]
| '(' expression ')'
````
Period must be an integer multiple of 60. The default period is 60 seconds.
Period must be an integer multiple of 60. The default period is 60 seconds.
Expression is by default **non-deterministic** (i.e. when expression does
not contain *deterministic* keyword). If however **deterministic**
option would be desired, it is enough to have *deterministic* keyword
inside expression.
The logical_operators are: `and` (also `&&`), `or` (also `||`).
@ -603,6 +660,22 @@ In this example a compound alarm expression is evaluated involving two threshold
avg(cpu.system_perc{hostname=hostname.domain.com}) > 90 or avg(disk_read_ops{hostname=hostname.domain.com, device=vda}, 120) > 1000
```
#### Deterministic alarm example
In this example alarm is created with one expression which is deterministic
```
count(log.error{}, deterministic) > 1
```
#### Non-deterministic alarm with deterministic sub expressions
In this example alarm's expression is composed of 3 parts where two of them
are marked as **deterministic**. However entire expression is non-deterministic because
of the 3rd expression.
```
count(log.error{}, deterministic) > 1 or count(log.warning{}, deterministic) > 1 and avg(cpu.user_perc{}) > 10
```
### Changing Alarm Definitions
Once an Alarm Definition has been created, the value for match_by and any metrics in the expression cannot be changed. This is because those fields control the metrics used to create Alarms and Alarms may already have been created. The function, operator, period, periods and any boolean operators can change, but not the metrics in subexpressions or the number of subexpressions. All other fields in an Alarm Definition can be changed.
@ -1715,6 +1788,34 @@ Cache-Control: no-cache
}
```
To create deterministic definition following request should be sent:
```
POST /v2.0/alarm-definitions HTTP/1.1
Host: 192.168.10.4:8080
Content-Type: application/json
X-Auth-Token: 2b8882ba2ec44295bf300aecb2caa4f7
Cache-Control: no-cache
{
"name":"Average CPU percent greater than 10",
"description":"The average CPU percent is greater than 10",
"expression":"(avg(cpu.user_perc{hostname=devstack},deterministic) > 10)",
"match_by":[
"hostname"
],
"severity":"LOW",
"ok_actions":[
"c60ec47e-5038-4bf1-9f95-4046c6e9a759"
],
"alarm_actions":[
"c60ec47e-5038-4bf1-9f95-4046c6e9a759"
],
"undetermined_actions":[
"c60ec47e-5038-4bf1-9f95-4046c6e9a759"
]
}
```
### Response
#### Status Code
* 201 - Created
@ -1727,6 +1828,7 @@ Returns a JSON object of alarm definition objects with the following fields:
* name (string) - Name of alarm definition.
* description (string) - Description of alarm definition.
* expression (string) - The alarm definition expression.
* deterministic (boolean) - Is the underlying expression deterministic ? **Read-only**, computed from *expression*
* expression_data (JSON object) - The alarm definition expression as a JSON object.
* match_by ([string]) - The metric dimensions to match to the alarm dimensions
* severity (string) - The severity of an alarm definition. Either `LOW`, `MEDIUM`, `HIGH` or `CRITICAL`.
@ -1748,6 +1850,7 @@ Returns a JSON object of alarm definition objects with the following fields:
"name":"Average CPU percent greater than 10",
"description":"The average CPU percent is greater than 10",
"expression":"(avg(cpu.user_perc{hostname=devstack}) > 10)",
"deterministic": false,
"expression_data":{
"function":"AVG",
"metric_name":"cpu.user_perc",
@ -1819,6 +1922,7 @@ Returns a JSON object with a 'links' array of links and an 'elements' array of a
* name (string) - Name of alarm definition.
* description (string) - Description of alarm definition.
* expression (string) - The alarm definition expression.
* deterministic (boolean) - Is the underlying expression deterministic ? **Read-only**, computed from *expression*
* expression_data (JSON object) - The alarm definition expression as a JSON object.
* match_by ([string]) - The metric dimensions to use to create unique alarms
* severity (string) - The severity of an alarm definition. Either `LOW`, `MEDIUM`, `HIGH` or `CRITICAL`.
@ -1852,6 +1956,7 @@ Returns a JSON object with a 'links' array of links and an 'elements' array of a
"name": "CPU percent greater than 10",
"description": "Release the hounds",
"expression": "(avg(cpu.user_perc{hostname=devstack}) > 10)",
"deterministic": false,
"expression_data": {
"function": "AVG",
"metric_name": "cpu.user_perc",
@ -1877,6 +1982,38 @@ Returns a JSON object with a 'links' array of links and an 'elements' array of a
"undetermined_actions": [
"c60ec47e-5038-4bf1-9f95-4046c6e9a759"
]
},
{
"id": "g9323232-6543-4cbf-1234-0993a947ea83",
"links": [
{
"rel": "self",
"href": "http://192.168.10.4:8080/v2.0/alarm-definitions/g9323232-6543-4cbf-1234-0993a947ea83"
}
],
"name": "Log error count exceeds 1000",
"description": "Release the cats",
"expression": "(count(log.error{hostname=devstack}, deterministic) > 1000)",
"deterministic": true,
"expression_data": {
"function": "AVG",
"metric_name": "log.error",
"dimensions": {
"hostname": "devstack"
},
"operator": "GT",
"threshold": 1000,
"period": 60,
"periods": 1
},
"match_by": [
"hostname"
],
"severity": "CRITICAL",
"actions_enabled": true,
"alarm_actions": [],
"ok_actions": [],
"undetermined_actions": []
}
]
}
@ -1913,6 +2050,7 @@ Returns a JSON alarm definition object with the following fields:
* name (string) - Name of alarm definition.
* description (string) - Description of alarm definition.
* expression (string) - The alarm definition expression.
* deterministic (boolean) - Is the underlying expression deterministic ? **Read-only**, computed from *expression*
* expression_data (JSON object) - The alarm definition expression as a JSON object.
* match_by ([string]) - The metric dimensions to use to create unique alarms
* severity (string) - The severity of an alarm definition. Either `LOW`, `MEDIUM`, `HIGH` or `CRITICAL`.
@ -1934,6 +2072,7 @@ Returns a JSON alarm definition object with the following fields:
"name": "CPU percent greater than 10",
"description": "Release the hounds",
"expression": "(avg(cpu.user_perc{hostname=devstack}) > 10)",
"deterministic": false,
"expression_data": {
"function": "AVG",
"metric_name": "cpu.user_perc",
@ -2035,6 +2174,7 @@ Returns a JSON alarm definition object with the following parameters:
* name (string) - Name of alarm definition.
* description (string) - Description of alarm definition.
* expression (string) - The alarm definition expression.
* deterministic (boolean) - Is the underlying expression deterministic ? **Read-only**, computed from *expression*
* expression_data (JSON object) - The alarm definition expression as a JSON object.
* match_by ([string]) - The metric dimensions to use to create unique alarms
* severity (string) - The severity of an alarm definition. Either `LOW`, `MEDIUM`, `HIGH` or `CRITICAL`.
@ -2056,6 +2196,7 @@ Returns a JSON alarm definition object with the following parameters:
"name": "CPU percent greater than 15",
"description": "Release the hounds",
"expression": "(avg(cpu.user_perc{hostname=devstack}) > 15)",
"deterministic": false,
"expression_data": {
"function": "AVG",
"metric_name": "cpu.user_perc",
@ -2086,7 +2227,7 @@ ___
## Patch Alarm Definition
### PATCH /v2.0/alarm-definitions/{alarm_definition_id}
Update select parameters of the specified alarm definition, and enable/disable its actions.
Update selected parameters of the specified alarm definition, and enable/disable its actions.
#### Headers
* X-Auth-Token (string, required) - Keystone auth token
@ -2154,6 +2295,7 @@ Returns a JSON alarm definition object with the following fields:
* name (string) - Name of alarm definition.
* description (string) - Description of alarm definition.
* expression (string) - The alarm definition expression.
* deterministic (boolean) - Is the underlying expression deterministic ? **Read-only**, computed from *expression*
* expression_data (JSON object) - The alarm definition expression as a JSON object.
* match_by ([string]) - The metric dimensions to use to create unique alarms
* severity (string) - The severity of an alarm definition. Either `LOW`, `MEDIUM`, `HIGH` or `CRITICAL`.
@ -2175,6 +2317,7 @@ Returns a JSON alarm definition object with the following fields:
"name": "CPU percent greater than 15",
"description": "Release the hounds",
"expression": "(avg(cpu.user_perc{hostname=devstack}) > 15)",
"deterministic": false,
"expression_data": {
"function": "AVG",
"metric_name": "cpu.user_perc",
@ -2461,7 +2604,7 @@ Returns a JSON object with a 'links' array of links and an 'elements' array of a
* reason (string) - The reason for the state transition.
* reason_data (string) - The reason for the state transition as a JSON object.
* timestamp (string) - The time in ISO 8601 combined date and time format in UTC when the state transition occurred.
* sub_alarms ({{string, string, string(255): string(255), string, string, string, string}, string, [string]) - The sub-alarms stated of when the alarm state transition occurred.
* sub_alarms ({{string, string, string(255): string(255), string, string, string, string, boolean}, string, [string]) - The sub-alarms stated of when the alarm state transition occurred.
#### Response Examples
```
@ -2505,7 +2648,8 @@ Returns a JSON object with a 'links' array of links and an 'elements' array of a
"operator": "GT",
"threshold": 15,
"period": 60,
"periods": 1
"periods": 1,
"deterministic": false
},
"sub_alarm_state": "OK",
"current_values": [
@ -2542,7 +2686,8 @@ Returns a JSON object with a 'links' array of links and an 'elements' array of a
"operator": "GT",
"threshold": 10,
"period": 60,
"periods": 3
"periods": 3,
"deterministic": false
},
"sub_alarm_state": "OK",
"current_values": [
@ -2581,7 +2726,8 @@ Returns a JSON object with a 'links' array of links and an 'elements' array of a
"operator": "GT",
"threshold": 10,
"period": 60,
"periods": 3
"periods": 3,
"deterministic": false
},
"sub_alarm_state": "ALARM",
"current_values": [
@ -2928,7 +3074,7 @@ Returns a JSON object with a 'links' array of links and an 'elements' array of a
* reason (string) - The reason for the state transition.
* reason_data (string) - The reason for the state transition as a JSON object.
* timestamp (string) - The time in ISO 8601 combined date and time format in UTC when the state transition occurred.
* sub_alarms ({{string, string, string(255): string(255), string, string, string, string}, string, [string]) - The sub-alarms stated of when the alarm state transition occurred.
* sub_alarms ({{string, string, string(255): string(255), string, string, string, string, boolean}, string, [string]) - The sub-alarms stated of when the alarm state transition occurred.
#### Response Examples
```
@ -2970,7 +3116,8 @@ Returns a JSON object with a 'links' array of links and an 'elements' array of a
"operator": "LT",
"threshold": 10,
"period": 60,
"periods": 3
"periods": 3,
"deterministic": false
},
"sub_alarm_state": "ALARM",
"current_values": [
@ -3007,7 +3154,8 @@ Returns a JSON object with a 'links' array of links and an 'elements' array of a
"operator": "LT",
"threshold": 10,
"period": 60,
"periods": 3
"periods": 3,
"deterministic": false
},
"sub_alarm_state": "OK",
"current_values": [
@ -3044,7 +3192,8 @@ Returns a JSON object with a 'links' array of links and an 'elements' array of a
"operator": "LT",
"threshold": 10,
"period": 60,
"periods": 3
"periods": 3,
"deterministic": false
},
"sub_alarm_state": "ALARM",
"current_values": [
@ -3095,4 +3244,3 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -32,6 +32,7 @@ public class AlarmDefinition extends AbstractEntity implements Linked {
private String name;
private String description = "";
private String expression;
private boolean deterministic;
private Object expressionData;
private List<String> matchBy;
private String severity;
@ -88,6 +89,9 @@ public class AlarmDefinition extends AbstractEntity implements Linked {
return false;
} else if (!expressionData.equals(other.expressionData))
return false;
if (this.deterministic != other.deterministic) {
return false;
}
if (links == null) {
if (other.links != null)
return false;
@ -174,6 +178,7 @@ public class AlarmDefinition extends AbstractEntity implements Linked {
result = prime * result + ((description == null) ? 0 : description.hashCode());
result = prime * result + ((expression == null) ? 0 : expression.hashCode());
result = prime * result + ((expressionData == null) ? 0 : expressionData.hashCode());
result = prime * result + Boolean.valueOf(this.deterministic).hashCode();
result = prime * result + ((links == null) ? 0 : links.hashCode());
result = prime * result + ((matchBy == null) ? 0 : matchBy.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
@ -201,7 +206,10 @@ public class AlarmDefinition extends AbstractEntity implements Linked {
public void setExpression(String expression) {
this.expression = expression;
setExpressionData(AlarmExpression.of(expression).getExpressionTree());
final AlarmExpression alarmExpression = AlarmExpression.of(expression);
this.setExpressionData(alarmExpression.getExpressionTree());
this.deterministic = alarmExpression.isDeterministic();
}
@JsonIgnore
@ -239,6 +247,10 @@ public class AlarmDefinition extends AbstractEntity implements Linked {
this.undeterminedActions = undeterminedActions;
}
public boolean isDeterministic() {
return this.deterministic;
}
@Override
public String toString() {
return String.format("AlarmDefinition [name=%s]", name);

View File

@ -451,13 +451,24 @@ public class AlarmDefinitionSqlRepoImpl
double threshold = subAlarmDef.getThreshold();
int period = subAlarmDef.getPeriod();
int periods = subAlarmDef.getPeriods();
boolean isDeterministic = subAlarmDef.isDeterministic();
Map<String, String> dimensions = Collections.emptyMap();
if (subAlarmDefDimensionMapExpression.containsKey(id)) {
dimensions = subAlarmDefDimensionMapExpression.get(id);
}
subExpressions.put(id, new AlarmSubExpression(function, new MetricDefinition(metricName, dimensions), operator, threshold, period, periods));
subExpressions.put(id,
new AlarmSubExpression(
function,
new MetricDefinition(metricName, dimensions),
operator,
threshold,
period,
periods,
isDeterministic
)
);
}
return subExpressions;
@ -545,6 +556,7 @@ public class AlarmDefinitionSqlRepoImpl
subAlarmDefinitionDb.setOperator(sa.getOperator().name());
subAlarmDefinitionDb.setThreshold(sa.getThreshold());
subAlarmDefinitionDb.setUpdatedAt(this.getUTCNow());
subAlarmDefinitionDb.setDeterministic(sa.isDeterministic());
session.saveOrUpdate(subAlarmDefinitionDb);
}
}
@ -580,6 +592,7 @@ public class AlarmDefinitionSqlRepoImpl
alarmDefinitionDb.setMatchBy(matchBy == null || Iterables.isEmpty(matchBy) ? null : COMMA_JOINER.join(matchBy));
alarmDefinitionDb.setSeverity(AlarmSeverity.valueOf(severity));
alarmDefinitionDb.setActionsEnabled(actionsEnabled);
alarmDefinitionDb.setUpdatedAt(this.getUTCNow());
session.saveOrUpdate(alarmDefinitionDb);
@ -715,7 +728,7 @@ public class AlarmDefinitionSqlRepoImpl
// Persist sub-alarm
final DateTime now = this.getUTCNow();
SubAlarmDefinitionDb subAlarmDefinitionDb = new SubAlarmDefinitionDb(
final SubAlarmDefinitionDb subAlarmDefinitionDb = new SubAlarmDefinitionDb(
subAlarmId,
alarmDefinition,
subExpr.getFunction().name(),
@ -725,7 +738,8 @@ public class AlarmDefinitionSqlRepoImpl
subExpr.getPeriod(),
subExpr.getPeriods(),
now,
now
now,
subExpr.isDeterministic()
);
session.save(subAlarmDefinitionDb);

View File

@ -1,11 +1,11 @@
/*
* (C) Copyright 2014,2016 Hewlett Packard Enterprise Development Company LP
*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
@ -60,6 +60,13 @@ public class AlarmDefinitionMySqlRepoImpl implements AlarmDefinitionRepo {
"select sa.*, sad.dimensions from sub_alarm_definition as sa "
+ "left join (select sub_alarm_definition_id, group_concat(dimension_name, '=', value) as dimensions from sub_alarm_definition_dimension group by sub_alarm_definition_id ) as sad "
+ "on sad.sub_alarm_definition_id = sa.id where sa.alarm_definition_id = :alarmDefId";
private static final String CREATE_SUB_EXPRESSION_SQL = "insert into sub_alarm_definition "
+ "(id, alarm_definition_id, function, metric_name, "
+ "operator, threshold, period, periods, is_deterministic, "
+ "created_at, updated_at) "
+ "values (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())";
private static final String UPDATE_SUB_ALARM_DEF_SQL = "update sub_alarm_definition set "
+ "operator = ?, threshold = ?, is_deterministic = ?, updated_at = NOW() where id = ?";
private final DBI db;
private final PersistUtils persistUtils;
@ -112,7 +119,7 @@ public class AlarmDefinitionMySqlRepoImpl implements AlarmDefinitionRepo {
"update alarm_definition set deleted_at = NOW() where tenant_id = ? and id = ? and deleted_at is NULL",
tenantId, alarmDefId) == 0)
throw new EntityNotFoundException("No alarm definition exists for %s", alarmDefId);
// Cascade soft delete to alarms
h.execute("delete from alarm where alarm_definition_id = :id", alarmDefId);
}
@ -280,10 +287,23 @@ public class AlarmDefinitionMySqlRepoImpl implements AlarmDefinitionRepo {
// Need to convert the results appropriately based on type.
Integer period = Conversions.variantToInteger(row.get("period"));
Integer periods = Conversions.variantToInteger(row.get("periods"));
Boolean isDeterministic = (Boolean) row.get("is_deterministic");
Map<String, String> dimensions =
DimensionQueries.dimensionsFor((String) row.get("dimensions"));
subExpressions.put(id, new AlarmSubExpression(function, new MetricDefinition(metricName,
dimensions), operator, threshold, period, periods));
subExpressions.put(
id,
new AlarmSubExpression(
function,
new MetricDefinition(metricName, dimensions),
operator,
threshold,
period,
periods,
isDeterministic
)
);
}
return subExpressions;
@ -315,8 +335,12 @@ public class AlarmDefinitionMySqlRepoImpl implements AlarmDefinitionRepo {
for (Map.Entry<String, AlarmSubExpression> entry : changedSubAlarms.entrySet()) {
AlarmSubExpression sa = entry.getValue();
h.execute(
"update sub_alarm_definition set operator = ?, threshold = ?, updated_at = NOW() where id = ?",
sa.getOperator().name(), sa.getThreshold(), entry.getKey());
UPDATE_SUB_ALARM_DEF_SQL,
sa.getOperator().name(),
sa.getThreshold(),
sa.isDeterministic(),
entry.getKey()
);
}
// Insert new sub-alarms
@ -365,12 +389,9 @@ public class AlarmDefinitionMySqlRepoImpl implements AlarmDefinitionRepo {
MetricDefinition metricDef = subExpr.getMetricDefinition();
// Persist sub-alarm
handle
.insert(
"insert into sub_alarm_definition (id, alarm_definition_id, function, metric_name, operator, threshold, period, periods, created_at, updated_at) "
+ "values (?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())", subAlarmId, id, subExpr
.getFunction().name(), metricDef.name, subExpr.getOperator().name(), subExpr
.getThreshold(), subExpr.getPeriod(), subExpr.getPeriods());
handle.insert(CREATE_SUB_EXPRESSION_SQL, subAlarmId, id, subExpr.getFunction().name(),
metricDef.name, subExpr.getOperator().name(), subExpr.getThreshold(),
subExpr.getPeriod(), subExpr.getPeriods(), subExpr.isDeterministic());
// Persist sub-alarm dimensions
if (metricDef.dimensions != null && !metricDef.dimensions.isEmpty())

View File

@ -54,4 +54,9 @@ public class AlarmExpressionsTest {
public void shouldThrowOnInvalidThreshold() throws Exception {
AlarmValidation.validateNormalizeAndGet("avg(hpcs.compute.net_out_bytes) > abc");
}
@Test(expectedExceptions = WebApplicationException.class)
public void shouldThrowOnMalformedDeterministicKeyword() throws Exception {
AlarmValidation.validateNormalizeAndGet("avg(hpcs.compute.net_out_bytes,determ) > 1");
}
}

View File

@ -57,7 +57,9 @@ import com.sun.jersey.api.client.ClientResponse;
@Test
public class AlarmDefinitionResourceTest extends AbstractMonApiResourceTest {
private String expression;
private String detExpression;
private AlarmDefinition alarm;
private AlarmDefinition detAlarm;
private AlarmDefinition alarmItem;
private AlarmDefinitionService service;
private AlarmDefinitionRepo repo;
@ -69,6 +71,7 @@ public class AlarmDefinitionResourceTest extends AbstractMonApiResourceTest {
super.setupResources();
expression = "avg(disk_read_ops{service=hpcs.compute, instance_id=937}) >= 90";
detExpression = "count(log.error{service=test,instance_id=2},deterministic) >= 10 times 10";
List<String> matchBy = Arrays.asList("service", "instance_id");
alarmItem =
new AlarmDefinition("123", "Disk Exceeds 1k Operations", null, "LOW", expression,
@ -76,18 +79,28 @@ public class AlarmDefinitionResourceTest extends AbstractMonApiResourceTest {
alarmActions = new ArrayList<String>();
alarmActions.add("29387234");
alarmActions.add("77778687");
alarm =
new AlarmDefinition("123", "Disk Exceeds 1k Operations", null, "LOW", expression, matchBy,
true, alarmActions, null, null);
detAlarm =
new AlarmDefinition("456", "log.error", null, "LOW", detExpression, matchBy,
true, alarmActions, null, null);
service = mock(AlarmDefinitionService.class);
when(
service.create(eq("abc"), eq("Disk Exceeds 1k Operations"), any(String.class), eq("LOW"),
eq(expression), eq(AlarmExpression.of(expression)), eq(matchBy), any(List.class),
any(List.class), any(List.class))).thenReturn(alarm);
when(
service.create(eq("abc"), eq("log.error"), any(String.class), eq("LOW"),
eq(detExpression), eq(AlarmExpression.of(detExpression)), eq(matchBy), any(List.class),
any(List.class), any(List.class))).thenReturn(detAlarm);
repo = mock(AlarmDefinitionRepo.class);
when(repo.findById(eq("abc"), eq("123"))).thenReturn(alarm);
when(repo.findById(eq("abc"), eq("456"))).thenReturn(detAlarm);
when(repo.find(anyString(), anyString(), (Map<String, String>) anyMap(), AlarmSeverity.fromString(anyString()),
(List<String>) anyList(), anyString(), anyInt())).thenReturn(
Arrays.asList(alarmItem));
@ -112,6 +125,31 @@ public class AlarmDefinitionResourceTest extends AbstractMonApiResourceTest {
any(List.class), any(List.class));
}
public void shouldCreateDeterministic() {
final CreateAlarmDefinitionCommand request = new CreateAlarmDefinitionCommand(
"log.error",
null,
detExpression,
Arrays.asList("service", "instance_id"),
"LOW",
alarmActions,
null,
null
);
final ClientResponse response = this.createResponseFor(request);
assertEquals(response.getStatus(), 201);
AlarmDefinition newAlarm = response.getEntity(AlarmDefinition.class);
String location = response.getHeaders().get("Location").get(0);
assertEquals(location, "/v2.0/alarm-definitions/" + newAlarm.getId());
assertEquals(newAlarm, detAlarm);
verify(service).create(eq("abc"), eq("log.error"), any(String.class),
eq("LOW"), eq(detExpression), eq(AlarmExpression.of(detExpression)),
eq(Arrays.asList("service", "instance_id")), any(List.class),
any(List.class), any(List.class));
}
public void shouldUpdate() {
when(
service.update(eq("abc"), eq("123"), any(AlarmExpression.class),

View File

@ -1 +1 @@
{"id":"123","links":[{"rel":"self","href":"https://cloudsvc.example.com/v1.0"}],"name":"90% CPU","description":"","expression":"avg(hpcs.compute{instance_id=666, image_id=345}) >= 90","match_by":[],"severity":"LOW","actions_enabled":false,"alarm_actions":["123345345","23423"],"ok_actions":null,"undetermined_actions":null}
{"id":"123","links":[{"rel":"self","href":"https://cloudsvc.example.com/v1.0"}],"name":"90% CPU","description":"","expression":"avg(hpcs.compute{instance_id=666, image_id=345}) >= 90","deterministic":false,"match_by":[],"severity":"LOW","actions_enabled":false,"alarm_actions":["123345345","23423"],"ok_actions":null,"undetermined_actions":null}

View File

@ -59,6 +59,7 @@ CREATE TABLE `sub_alarm_definition` (
`threshold` double NOT NULL,
`period` int(11) NOT NULL,
`periods` int(11) NOT NULL,
`is_deterministic` tinyint(1) NOT NULL DEFAULT '0',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`)

View File

@ -48,6 +48,7 @@ class SubAlarmDefinition(object):
self.periods = str(row['periods']).decode('utf8')
# threshold comes from the DB as a float in 0.0 form.
self.threshold = str(row['threshold']).decode('utf8')
self.deterministic = str(row['is_deterministic']) == '1'
if sub_expr:
# id is not used for compare or hash.
@ -62,6 +63,7 @@ class SubAlarmDefinition(object):
self.period = sub_expr.period
self.periods = sub_expr.periods
self.threshold = sub_expr.threshold
self.deterministic = sub_expr.deterministic
def _init_dimensions(self, dimensions_str):
@ -85,6 +87,9 @@ class SubAlarmDefinition(object):
if self.dimensions_str:
result += "{{{}}}".format(self.dimensions_str.encode('utf8'))
if self.deterministic:
result += ', deterministic'
if self.period:
result += ", {}".format(self.period.encode('utf8'))
@ -111,6 +116,7 @@ class SubAlarmDefinition(object):
hash(self.operator) ^
hash(self.period) ^
hash(self.periods) ^
hash(self.deterministic) ^
# Convert to float to handle cases like 0.0 == 0
hash(float(self.threshold)))
@ -130,12 +136,13 @@ class SubAlarmDefinition(object):
self.operator == other.operator and
self.period == other.period and
self.periods == other.periods and
self.deterministic == other.deterministic and
# Convert to float to handle cases like 0.0 == 0
float(self.threshold) == float(other.threshold))
def same_key_fields(self, other):
# compare everything but operator and threshold
# compare everything but operator, threshold and deterministic
return (self.metric_name == other.metric_name and
self.dimensions == other.dimensions and
self.function == other.function and

View File

@ -268,10 +268,11 @@ class AlarmDefinitionsRepository(mysql_repository.MySQLRepository,
threshold,
period,
periods,
is_deterministic,
created_at,
updated_at)
values(%s,%s,%s,%s,%s,%s,%s,%s,%s,
%s)""",
%s, %s)""",
(
sub_alarm_definition_id,
alarm_definition_id,
@ -281,7 +282,10 @@ class AlarmDefinitionsRepository(mysql_repository.MySQLRepository,
sub_expr.normalized_operator.encode('utf8'),
sub_expr.threshold.encode('utf8'),
sub_expr.period.encode('utf8'),
sub_expr.periods.encode('utf8'), now, now))
sub_expr.periods.encode('utf8'),
sub_expr.deterministic,
now,
now))
for dimension in sub_expr.dimensions_as_list:
parsed_dimension = dimension.split('=')
@ -463,13 +467,17 @@ class AlarmDefinitionsRepository(mysql_repository.MySQLRepository,
update sub_alarm_definition
set operator = %s,
threshold = %s,
updated_at = %s
is_deterministic = %s,
updated_at = %s,
where id = %s"""
for sub_alarm_definition_id, sub_alarm_def in (
changed_sub_alarm_defs_by_id.iteritems()):
parms = [sub_alarm_def.operator, sub_alarm_def.threshold,
now, sub_alarm_definition_id]
parms = [sub_alarm_def.operator,
sub_alarm_def.threshold,
sub_alarm_def.deterministic,
now,
sub_alarm_definition_id]
cursor.execute(query, parms)
# Insert new sub alarm definitions
@ -483,9 +491,10 @@ class AlarmDefinitionsRepository(mysql_repository.MySQLRepository,
threshold,
period,
periods,
is_deterministic,
created_at,
updated_at)
values(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)"""
values(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)"""
sub_query = """
insert into sub_alarm_definition_dimension(
@ -503,6 +512,7 @@ class AlarmDefinitionsRepository(mysql_repository.MySQLRepository,
str(sub_alarm_def.threshold).encode('utf8'),
str(sub_alarm_def.period).encode('utf8'),
str(sub_alarm_def.periods).encode('utf8'),
sub_alarm_def.deterministic,
now,
now]

View File

@ -187,6 +187,7 @@ class AlarmDefinitionsRepository(sql_repository.SQLRepository,
threshold=bindparam('b_threshold'),
period=bindparam('b_period'),
periods=bindparam('b_periods'),
is_deterministic=bindparam('b_is_deterministic'),
created_at=bindparam('b_created_at'),
updated_at=bindparam('b_updated_at')))
@ -209,6 +210,7 @@ class AlarmDefinitionsRepository(sql_repository.SQLRepository,
.values(
operator=bindparam('b_operator'),
threshold=bindparam('b_threshold'),
is_deterministic=bindparam('b_is_deterministic'),
updated_at=bindparam('b_updated_at')))
b_ad_id = bindparam('b_alarm_definition_id'),
@ -222,6 +224,7 @@ class AlarmDefinitionsRepository(sql_repository.SQLRepository,
threshold=bindparam('b_threshold'),
period=bindparam('b_period'),
periods=bindparam('b_periods'),
is_deterministic=bindparam('b_is_deterministic'),
created_at=bindparam('b_created_at'),
updated_at=bindparam('b_updated_at')))
@ -426,6 +429,7 @@ class AlarmDefinitionsRepository(sql_repository.SQLRepository,
b_threshold=sub_expr.threshold.encode('utf8'),
b_period=sub_expr.period.encode('utf8'),
b_periods=sub_expr.periods.encode('utf8'),
b_is_deterministic=sub_expr.deterministic,
b_created_at=now,
b_updated_at=now)
@ -574,6 +578,7 @@ class AlarmDefinitionsRepository(sql_repository.SQLRepository,
changed_sub_alarm_defs_by_id.iteritems()):
parms.append({'b_operator': sub_alarm_def.operator,
'b_threshold': sub_alarm_def.threshold,
'b_is_deterministic': sub_alarm_def.deterministic,
'b_updated_at': now,
'b_id': sub_alarm_definition_id})
if len(parms) > 0:
@ -590,6 +595,7 @@ class AlarmDefinitionsRepository(sql_repository.SQLRepository,
threshold = str(sub_alarm_def.threshold).encode('utf8')
period = str(sub_alarm_def.period).encode('utf8')
periods = str(sub_alarm_def.periods).encode('utf8')
is_deterministic = sub_alarm_def.is_deterministic
parms.append({'b_id': sub_alarm_def.id,
'b_alarm_definition_id': adi,
'b_function': function,
@ -598,6 +604,7 @@ class AlarmDefinitionsRepository(sql_repository.SQLRepository,
'b_threshold': threshold,
'b_period': period,
'b_periods': periods,
'b_is_deterministic': is_deterministic,
'b_created_at': now,
'b_updated_at': now})

View File

@ -126,6 +126,7 @@ def create_sad_model(metadata=None):
Column('threshold', Float),
Column('period', Integer),
Column('periods', Integer),
Column('is_deterministic', Boolean),
Column('created_at', DateTime),
Column('updated_at', DateTime))

View File

@ -17,6 +17,10 @@ import sys
import pyparsing
_DETERMINISTIC_ASSIGNMENT_LEN = 3
_DETERMINISTIC_ASSIGNMENT_SHORT_LEN = 1
_DETERMINISTIC_ASSIGNMENT_VALUE_INDEX = 2
class SubExpr(object):
@ -35,6 +39,7 @@ class SubExpr(object):
self._threshold = tokens.threshold
self._period = tokens.period
self._periods = tokens.periods
self._deterministic = tokens.deterministic
self._id = None
@property
@ -128,6 +133,10 @@ class SubExpr(object):
else:
return u'1'
@property
def deterministic(self):
return True if self._deterministic else False
@property
def normalized_operator(self):
"""Get the operator as one of LT, GT, LTE, or GTE."""
@ -237,8 +246,16 @@ period = integer_number("period")
threshold = decimal_number("threshold")
periods = integer_number("periods")
function_and_metric = (func + LPAREN + metric + pyparsing.Optional(
COMMA + period) + RPAREN)
deterministic = (
pyparsing.CaselessLiteral('deterministic')
)('deterministic')
function_and_metric = (
func + LPAREN + metric +
pyparsing.Optional(COMMA + deterministic) +
pyparsing.Optional(COMMA + period) +
RPAREN
)
expression = pyparsing.Forward()
@ -291,6 +308,10 @@ def main():
"ntp.offset > 1 or ntp.offset < -5",
"max(3test_metric5{it's this=that's it}) lt 5 times 3",
"count(log.error{test=1}, deterministic) > 1.0",
"count(log.error{test=1}, deterministic, 120) > 1.0"
]
for expr in expr_list:
@ -306,6 +327,10 @@ def main():
sub_expr.fmtd_sub_expr_str.encode('utf8')))
print('sub_expr dimensions: {}'.format(
sub_expr.dimensions_str.encode('utf8')))
print('sub_expr deterministic: {}'.format(
sub_expr.deterministic))
print('sub_expr period: {}'.format(
sub_expr.period))
print("")
print("")

View File

@ -86,6 +86,7 @@ CREATE TABLE `sub_alarm_definition` (
`threshold` double NOT NULL,
`period` int(11) NOT NULL,
`periods` int(11) NOT NULL,
`is_deterministic` tinyint(1) NOT NULL DEFAULT(0),
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`)

View File

@ -83,6 +83,7 @@ class TestAlarmDefinitionRepoDB(testtools.TestCase, fixtures.TestWithFixtures):
threshold=bindparam('threshold'),
period=bindparam('period'),
periods=bindparam('periods'),
is_deterministic=bindparam('is_deterministic'),
created_at=bindparam('created_at'),
updated_at=bindparam('updated_at')))
@ -152,6 +153,7 @@ class TestAlarmDefinitionRepoDB(testtools.TestCase, fixtures.TestWithFixtures):
'threshold': 10,
'period': 60,
'periods': 1,
'is_deterministic': False,
'created_at': datetime.datetime.now(),
'updated_at': datetime.datetime.now()},
{'id': '222',
@ -162,6 +164,7 @@ class TestAlarmDefinitionRepoDB(testtools.TestCase, fixtures.TestWithFixtures):
'threshold': 20,
'period': 60,
'periods': 1,
'is_deterministic': False,
'created_at': datetime.datetime.now(),
'updated_at': datetime.datetime.now()},
{'id': '223',
@ -172,6 +175,7 @@ class TestAlarmDefinitionRepoDB(testtools.TestCase, fixtures.TestWithFixtures):
'threshold': 100,
'period': 60,
'periods': 1,
'is_deterministic': False,
'created_at': datetime.datetime.now(),
'updated_at': datetime.datetime.now()},
]
@ -317,6 +321,7 @@ class TestAlarmDefinitionRepoDB(testtools.TestCase, fixtures.TestWithFixtures):
'operator': 'GT',
'period': 60,
'periods': 1,
'is_deterministic': False,
'threshold': 20.0},
{'alarm_definition_id': '234',
'dimensions': None,
@ -326,6 +331,7 @@ class TestAlarmDefinitionRepoDB(testtools.TestCase, fixtures.TestWithFixtures):
'operator': 'LT',
'period': 60,
'periods': 1,
'is_deterministic': False,
'threshold': 100.0}]
self.assertEqual(len(sub_alarms), len(expected))
@ -477,6 +483,7 @@ class TestAlarmDefinitionRepoDB(testtools.TestCase, fixtures.TestWithFixtures):
'operator': 'GT',
'period': 60,
'periods': 1,
'is_deterministic': False,
'threshold': 10.0}]
self.assertEqual(len(sub_alarms), len(expected))
@ -497,6 +504,7 @@ class TestAlarmDefinitionRepoDB(testtools.TestCase, fixtures.TestWithFixtures):
'operator': 'GT',
'period': 60,
'periods': 1,
'is_deterministic': False,
'threshold': 20.0},
{'alarm_definition_id': '234',
'dimensions': None,
@ -506,6 +514,7 @@ class TestAlarmDefinitionRepoDB(testtools.TestCase, fixtures.TestWithFixtures):
'operator': 'LT',
'period': 60,
'periods': 1,
'is_deterministic': False,
'threshold': 100.0}]
self.assertEqual(len(sub_alarms), len(expected))

View File

@ -233,6 +233,7 @@ class TestAlarmDefinition(AlarmTestBase):
u'actions_enabled': u'true',
u'undetermined_actions': [],
u'expression': u'test.metric > 10',
u'deterministic': False,
u'id': u'00000001-0001-0001-0001-000000000001',
u'severity': u'LOW',
}
@ -285,6 +286,7 @@ class TestAlarmDefinition(AlarmTestBase):
u'actions_enabled': u'true',
u'undetermined_actions': [],
u'expression': u'test.metric > 10',
u'deterministic': False,
u'id': u'00000001-0001-0001-0001-000000000001',
u'severity': u'LOW',
}
@ -334,6 +336,7 @@ class TestAlarmDefinition(AlarmTestBase):
u'name': u'Test Alarm',
u'actions_enabled': True,
u'undetermined_actions': [],
u'is_deterministic': False,
u'expression': u'max(test.metric{hostname=host}) gte 1',
u'id': u'00000001-0001-0001-0001-000000000001',
u'severity': u'LOW'},
@ -346,6 +349,7 @@ class TestAlarmDefinition(AlarmTestBase):
'operator': 'gte',
'threshold': 1,
'period': 60,
'is_deterministic': False,
'periods': 1})},
'changed': {},
'new': {},
@ -358,6 +362,7 @@ class TestAlarmDefinition(AlarmTestBase):
'operator': 'gte',
'threshold': 1,
'period': 60,
'is_deterministic': False,
'periods': 1})}
}
)
@ -374,6 +379,7 @@ class TestAlarmDefinition(AlarmTestBase):
u'name': u'Test Alarm',
u'actions_enabled': True,
u'undetermined_actions': [],
u'deterministic': False,
u'expression': u'max(test.metric{hostname=host}) gte 1',
u'severity': u'LOW',
}
@ -386,6 +392,7 @@ class TestAlarmDefinition(AlarmTestBase):
u'name': u'Test Alarm',
u'actions_enabled': True,
u'undetermined_actions': [],
u'deterministic': False,
u'expression': u'max(test.metric{hostname=host}) gte 1',
u'severity': u'LOW',
}
@ -411,6 +418,7 @@ class TestAlarmDefinition(AlarmTestBase):
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',
@ -421,7 +429,8 @@ class TestAlarmDefinition(AlarmTestBase):
'operator': 'gte',
'threshold': 1,
'period': 60,
'periods': 1})},
'periods': 1,
'is_deterministic': False})},
'changed': {},
'new': {},
'unchanged': {'11111': sub_alarm_definition.SubAlarmDefinition(
@ -433,7 +442,8 @@ class TestAlarmDefinition(AlarmTestBase):
'operator': 'gte',
'threshold': 1,
'period': 60,
'periods': 1})}
'periods': 1,
'is_deterministic': False})}
}
)
@ -451,6 +461,7 @@ class TestAlarmDefinition(AlarmTestBase):
u'undetermined_actions': [],
u'expression': u'max(test.metric{hostname=host}) gte 1',
u'severity': u'LOW',
u'deterministic': False
}
alarm_def = {
@ -462,7 +473,7 @@ class TestAlarmDefinition(AlarmTestBase):
u'actions_enabled': True,
u'undetermined_actions': [],
u'expression': u'max(test.metric{hostname=host}) gte 1',
u'severity': u'LOW',
u'severity': u'LOW'
}
result = self.simulate_request("/v2.0/alarm-definitions/%s" % expected_def[u'id'],
@ -495,6 +506,7 @@ class TestAlarmDefinition(AlarmTestBase):
'name': u'Test Alarm',
'actions_enabled': 1,
'undetermined_actions': None,
'deterministic': False,
'expression': u'max(test.metric{hostname=host}) gte 1',
'id': u'00000001-0001-0001-0001-000000000001',
'severity': u'LOW'
@ -508,6 +520,7 @@ class TestAlarmDefinition(AlarmTestBase):
u'name': u'Test Alarm',
u'actions_enabled': True,
u'undetermined_actions': [],
u'deterministic': False,
u'expression': u'max(test.metric{hostname=host}) gte 1',
u'id': u'00000001-0001-0001-0001-000000000001',
u'severity': u'LOW',
@ -546,6 +559,7 @@ class TestAlarmDefinition(AlarmTestBase):
u'name': u'Test Alarm',
u'actions_enabled': True,
u'undetermined_actions': [],
u'deterministic': False,
u'expression': u'max(test.metric{hostname=host}) gte 1',
u'id': u'00000001-0001-0001-0001-000000000001',
u'severity': u'LOW',

View File

@ -138,7 +138,7 @@ cfg.CONF.register_opts(mysql_opts, mysql_group)
sql_opts = [cfg.StrOpt('url', default=None), cfg.StrOpt('host', default=None),
cfg.StrOpt('username', default=None), cfg.StrOpt('password', default=None),
cfg.StrOpt('drivername', default=None), cfg.PortOpt('port', default=None),
cfg.StrOpt('drivername', default=None), cfg.IntOpt('port', default=None),
cfg.StrOpt('database', default=None), cfg.StrOpt('query', default=None)]
sql_group = cfg.OptGroup(name='database', title='sql')

View File

@ -263,13 +263,17 @@ class AlarmDefinitions(alarm_definitions_api_v2.AlarmDefinitionsV2API,
description = (alarm_definition_row['description'].decode('utf8')
if alarm_definition_row['description'] is not None else None)
expression = alarm_definition_row['expression'].decode('utf8')
is_deterministic = is_definition_deterministic(expression)
result = {
u'actions_enabled': alarm_definition_row['actions_enabled'] == 1,
u'alarm_actions': alarm_actions_list,
u'undetermined_actions': undetermined_actions_list,
u'ok_actions': ok_actions_list,
u'description': description,
u'expression': alarm_definition_row['expression'].decode('utf8'),
u'expression': expression,
u'deterministic': is_deterministic,
u'id': alarm_definition_row['id'].decode('utf8'),
u'match_by': match_by,
u'name': alarm_definition_row['name'].decode('utf8'),
@ -321,11 +325,14 @@ class AlarmDefinitions(alarm_definitions_api_v2.AlarmDefinitionsV2API,
undetermined_actions_list = get_comma_separated_str_as_list(
alarm_definition_row['undetermined_actions'])
expression = alarm_definition_row['expression']
is_deterministic = is_definition_deterministic(expression)
ad = {u'id': alarm_definition_row['id'],
u'name': alarm_definition_row['name'],
u'description': alarm_definition_row['description'] if (
alarm_definition_row['description']) else u'',
u'expression': alarm_definition_row['expression'],
u'deterministic': is_deterministic,
u'match_by': match_by,
u'severity': alarm_definition_row['severity'].upper(),
u'actions_enabled':
@ -512,6 +519,7 @@ class AlarmDefinitions(alarm_definitions_api_v2.AlarmDefinitionsV2API,
u'severity': severity, u'actions_enabled': u'true',
u'undetermined_actions': undetermined_actions,
u'expression': expression, u'id': alarm_definition_id,
u'deterministic': is_definition_deterministic(expression),
u'name': name})
return result
@ -707,3 +715,27 @@ def get_comma_separated_str_as_list(comma_separated_str):
return []
else:
return comma_separated_str.decode('utf8').split(',')
def is_definition_deterministic(expression):
"""Evaluates if found expression is deterministic or not.
In order to do that expression is parsed into sub expressions.
Each sub expression needs to be deterministic in order for
entity expression to be such.
Otherwise expression is non-deterministic.
:param str expression: expression to be evaluated
:return: true/false
:rtype: bool
"""
expr_parser = (monasca_api.expression_parser
.alarm_expr_parser.AlarmExprParser(expression))
sub_expressions = expr_parser.sub_expr_list
for sub_expr in sub_expressions:
if not sub_expr.deterministic:
return False
return True

View File

@ -147,6 +147,69 @@ class TestAlarmDefinitions(base.BaseMonascaTest):
alarm_definition)
self._delete_notification(notification_id)
@test.attr(type='gate')
def test_create_deterministic_alarm_definition(self):
name = data_utils.rand_name('log.error')
expression = "count(log.error{},deterministic) > 0"
alarm_definition = helpers.create_alarm_definition(
name=name,
description="description",
expression=expression,
match_by=['hostname'],
severity="MEDIUM"
)
resp, response_body = self.monasca_client.create_alarm_definitions(
alarm_definition
)
self._verify_create_alarm_definitions(resp,
response_body,
alarm_definition,
deterministic=True)
@test.attr(type='gate')
def test_create_non_deterministic_alarm_definition_compound_mixed_expr(self):
name = data_utils.rand_name('log.error.and.disk.used_perc')
expression = ('max(disk.used_perc{hostname=node_1}) > 99.0 AND '
'count(log.error{hostname=node_1},deterministic) > 0')
alarm_definition = helpers.create_alarm_definition(
name=name,
description="description",
expression=expression,
match_by=['hostname'],
severity="MEDIUM"
)
resp, response_body = self.monasca_client.create_alarm_definitions(
alarm_definition
)
self._verify_create_alarm_definitions(resp,
response_body,
alarm_definition,
deterministic=False)
@test.attr(type='gate')
def test_create_deterministic_alarm_definition_compound_expr(self):
name = data_utils.rand_name('log.error.nodes_1_2')
expression = ('count(log.error{hostname=node_2},deterministic) > 0 '
'AND '
'count(log.error{hostname=node_1},deterministic) > 0')
alarm_definition = helpers.create_alarm_definition(
name=name,
description="description",
expression=expression,
match_by=['hostname'],
severity="MEDIUM"
)
resp, response_body = self.monasca_client.create_alarm_definitions(
alarm_definition
)
self._verify_create_alarm_definitions(resp,
response_body,
alarm_definition,
deterministic=True)
@test.attr(type="gate")
@test.attr(type=['negative'])
def test_create_alarm_definition_with_special_chars_in_expression(self):
@ -815,6 +878,8 @@ class TestAlarmDefinitions(base.BaseMonascaTest):
res_body_create_alarm_def['match_by'])
self.assertEqual(response_body['severity'],
res_body_create_alarm_def['severity'])
self.assertEqual(response_body['deterministic'],
res_body_create_alarm_def['deterministic'])
def _verify_element_set(self, element):
self.assertTrue(set(['id',
@ -822,6 +887,7 @@ class TestAlarmDefinitions(base.BaseMonascaTest):
'name',
'description',
'expression',
'deterministic',
'match_by',
'severity',
'actions_enabled',
@ -836,13 +902,18 @@ class TestAlarmDefinitions(base.BaseMonascaTest):
self.assertTrue(set(['rel', 'href']) == set(link))
self.assertEqual(link['rel'], u'self')
def _verify_create_alarm_definitions(self, resp, response_body,
alarm_definition):
def _verify_create_alarm_definitions(self,
resp,
response_body,
alarm_definition,
deterministic=False):
self.assertEqual(201, resp.status)
self.assertEqual(alarm_definition['name'], response_body['name'])
self.assertEqual(alarm_definition['expression'],
str(response_body['expression']))
self.assertEqual(deterministic, bool(response_body['deterministic']))
if 'description' in alarm_definition:
self.assertEqual(alarm_definition['description'],
str(response_body['description']))

View File

@ -739,6 +739,64 @@ class TestAlarms(base.BaseMonascaTest):
if i != j:
self.assertNotEqual(dimensions[i], dimensions[j])
@test.attr(type="gate")
def test_verify_deterministic_alarm(self):
metric_name = data_utils.rand_name('log.fancy')
metric_dimensions = {'service': 'monitoring',
'hostname': 'mini-mon'}
name = data_utils.rand_name('alarm_definition')
expression = ('count(%s{service=monitoring},deterministic) > 10'
% metric_name)
match_by = ['hostname', 'device']
description = 'deterministic'
alarm_definition = helpers.create_alarm_definition(
name=name, description=description,
expression=expression, match_by=match_by)
resp, response_body = self.monasca_client.create_alarm_definitions(
alarm_definition)
alarm_definition_id = response_body['id']
query_param = '?alarm_definition_id=' + str(alarm_definition_id)
# 1. ensure alarm was not created
resp, response_body = self.monasca_client.list_alarms(query_param)
self._verify_list_alarms_elements(resp, response_body, 0)
# 2. put some metrics here to create it, should be in ok
metrics_count = 5
for it in range(0, metrics_count):
metric = helpers.create_metric(name=metric_name,
value=1.0,
dimensions=metric_dimensions)
self.monasca_client.create_metrics(metric)
self._wait_for_alarms(1, alarm_definition_id)
resp, response_body = self.monasca_client.list_alarms(query_param)
self._verify_list_alarms_elements(resp, response_body, 1)
element = response_body['elements'][0]
self.assertEqual('OK', element['state'])
# 3. exceed threshold
metrics_count = 20
for it in range(0, metrics_count):
metric = helpers.create_metric(name=metric_name,
value=1.0,
dimensions=metric_dimensions)
self.monasca_client.create_metrics(metric)
self._wait_for_alarms(1, alarm_definition_id)
resp, response_body = self.monasca_client.list_alarms(query_param)
self._verify_list_alarms_elements(resp, response_body, 1)
element = response_body['elements'][0]
self.assertEqual('ALARM', element['state'])
def _verify_list_alarms_elements(self, resp, response_body,
expect_num_elements):
self.assertEqual(200, resp.status)