##// END OF EJS Templates
rule engine: add support for NOT rule
ergo -
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,43 +1,47 b''
1 1 # appenlight README
2 2
3 3
4 4 To run the app you need to have meet prerequsites:
5 5
6 6 - running elasticsearch (2.3+ tested)
7 7 - running postgresql (9.5+ required)
8 8 - running redis
9 9
10 10 # Setup basics
11 11
12 12 Set up the basic application database schema:
13 13
14 14 appenlight_initialize_db config.ini
15 15
16 16 Set up basic elasticsearch schema:
17 17
18 18 appenlight-reindex-elasticsearch -c config.ini -t all
19 19
20 20 Installed the appenlight uptime plugin
21 21
22 22 # Running
23 23
24 24 To run the application itself:
25 25
26 26 pserve --reload development.ini
27 27
28 28 To run celery queue processing:
29 29
30 30 celery worker -A appenlight.celery -Q "reports,logs,metrics,default" --ini=development.ini
31 31
32 To run celery beats scheduling:
33
34 celery beat -A appenlight.celery --ini=development.ini
35
32 36 You should also run the channelstream websocket server for real-time notifications
33 37
34 38 channelstream -i filename.ini
35 39
36 40 # Testing
37 41
38 42 To run test suite:
39 43
40 44 py.test appenlight/tests/tests.py --cov appenlight (this looks for testing.ini in repo root)
41 45
42 46 WARNING!!!
43 47 Some tests will insert data into elasticsearch or redis based on testing.ini
@@ -1,288 +1,303 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # AppEnlight Enterprise Edition, including its added features, Support
19 19 # services, and proprietary license terms, please see
20 20 # https://rhodecode.com/licenses/
21 21
22 22 import logging
23 23 import operator
24 24
25 25 log = logging.getLogger(__name__)
26 26
27 27
28 28 class RuleException(Exception):
29 29 pass
30 30
31 31
32 32 class KeyNotFoundException(RuleException):
33 33 pass
34 34
35 35
36 36 class UnknownTypeException(RuleException):
37 37 pass
38 38
39 39
40 40 class BadConfigException(RuleException):
41 41 pass
42 42
43 43
44 44 class InvalidValueException(RuleException):
45 45 pass
46 46
47 47
48 48 class RuleBase(object):
49 49 @classmethod
50 50 def default_dict_struct_getter(cls, struct, field_name):
51 51 """
52 52 returns a key from dictionary based on field_name, if the name contains
53 53 `:` then it means additional nesting levels should be checked for the
54 54 key so `a:b:c` means return struct['a']['b']['c']
55 55
56 56 :param struct:
57 57 :param field_name:
58 58 :return:
59 59 """
60 60 parts = field_name.split(':') if field_name else []
61 61 found = struct
62 62 while parts:
63 63 current_key = parts.pop(0)
64 64 found = found.get(current_key)
65 65 if not found and parts:
66 66 raise KeyNotFoundException('Key not found in structure')
67 67 return found
68 68
69 69 @classmethod
70 70 def default_obj_struct_getter(cls, struct, field_name):
71 71 """
72 72 returns a key from instance based on field_name, if the name contains
73 73 `:` then it means additional nesting levels should be checked for the
74 74 key so `a:b:c` means return struct.a.b.c
75 75
76 76 :param struct:
77 77 :param field_name:
78 78 :return:
79 79 """
80 80 parts = field_name.split(':')
81 81 found = struct
82 82 while parts:
83 83 current_key = parts.pop(0)
84 84 found = getattr(found, current_key, None)
85 85 if not found and parts:
86 86 raise KeyNotFoundException('Key not found in structure')
87 87 return found
88 88
89 89 def normalized_type(self, field, value):
90 90 """
91 91 Converts text values from self.conf_value based on type_matrix below
92 92 check_matrix defines what kind of checks we can perform on a field
93 93 value based on field name
94 94 """
95 95 f_type = self.type_matrix.get(field)
96 96 if f_type:
97 97 cast_to = f_type['type']
98 98 else:
99 99 raise UnknownTypeException('Unknown type')
100 100
101 101 if value is None:
102 102 return None
103 103
104 104 try:
105 105 if cast_to == 'int':
106 106 return int(value)
107 107 elif cast_to == 'float':
108 108 return float(value)
109 109 elif cast_to == 'unicode':
110 110 return str(value)
111 111 except ValueError as exc:
112 112 raise InvalidValueException(exc)
113 113
114 114
115 115 class Rule(RuleBase):
116 116 def __init__(self, config, type_matrix,
117 117 struct_getter=RuleBase.default_dict_struct_getter,
118 118 config_manipulator=None):
119 119 """
120 120
121 121 :param config: dict - contains rule configuration
122 122 example::
123 123 {
124 124 "field": "__OR__",
125 125 "rules": [
126 126 {
127 127 "field": "__AND__",
128 128 "rules": [
129 129 {
130 130 "op": "ge",
131 131 "field": "occurences",
132 132 "value": "10"
133 133 },
134 134 {
135 135 "op": "ge",
136 136 "field": "priority",
137 137 "value": "4"
138 138 }
139 139 ]
140 140 },
141 141 {
142 142 "op": "eq",
143 143 "field": "http_status",
144 144 "value": "500"
145 145 }
146 146 ]
147 147 }
148 148 :param type_matrix: dict - contains map of type casts
149 149 example::
150 150 {
151 151 'http_status': 'int',
152 152 'priority': 'unicode',
153 153 }
154 154 :param struct_getter: callable - used to grab the value of field from
155 155 the structure passed to match() based
156 156 on key, default
157 157
158 158 """
159 159 self.type_matrix = type_matrix
160 160 self.config = config
161 161 self.struct_getter = struct_getter
162 162 self.config_manipulator = config_manipulator
163 163 if config_manipulator:
164 164 config_manipulator(self)
165 165
166 166 def subrule_check(self, rule_config, struct):
167 167 rule = Rule(rule_config, self.type_matrix,
168 168 config_manipulator=self.config_manipulator)
169 169 return rule.match(struct)
170 170
171 171 def match(self, struct):
172 172 """
173 173 Check if rule matched for this specific report
174 174 First tries report value, then tests tags in not found, then finally
175 175 report group
176 176 """
177 177 field_name = self.config.get('field')
178 178 test_value = self.config.get('value')
179 179
180 180 if not field_name:
181 181 return False
182 182
183 183 if field_name == '__AND__':
184 184 rule = AND(self.config['rules'], self.type_matrix,
185 185 config_manipulator=self.config_manipulator)
186 186 return rule.match(struct)
187 187 elif field_name == '__OR__':
188 188 rule = OR(self.config['rules'], self.type_matrix,
189 189 config_manipulator=self.config_manipulator)
190 190 return rule.match(struct)
191 elif field_name == '__NOT__':
192 rule = NOT(self.config['rules'], self.type_matrix,
193 config_manipulator=self.config_manipulator)
194 return rule.match(struct)
191 195
192 196 if test_value is None:
193 197 return False
194 198
195 199 try:
196 200 struct_value = self.normalized_type(field_name,
197 201 self.struct_getter(struct,
198 202 field_name))
199 203 except (UnknownTypeException, InvalidValueException) as exc:
200 204 log.error(str(exc))
201 205 return False
202 206
203 207 try:
204 208 test_value = self.normalized_type(field_name, test_value)
205 209 except (UnknownTypeException, InvalidValueException) as exc:
206 210 log.error(str(exc))
207 211 return False
208 212
209 213 if self.config['op'] not in ('startswith', 'endswith', 'contains'):
210 214 try:
211 215 return getattr(operator,
212 216 self.config['op'])(struct_value, test_value)
213 217 except TypeError:
214 218 return False
215 219 elif self.config['op'] == 'startswith':
216 220 return struct_value.startswith(test_value)
217 221 elif self.config['op'] == 'endswith':
218 222 return struct_value.endswith(test_value)
219 223 elif self.config['op'] == 'contains':
220 224 return test_value in struct_value
221 225 raise BadConfigException('Invalid configuration, '
222 226 'unknown operator: {}'.format(self.config))
223 227
224 228 def __repr__(self):
225 229 return '<Rule {} {}>'.format(self.config.get('field'),
226 230 self.config.get('value'))
227 231
228 232
229 233 class AND(Rule):
230 234 def __init__(self, rules, *args, **kwargs):
231 235 super(AND, self).__init__({}, *args, **kwargs)
232 236 self.rules = rules
233 237
234 238 def match(self, struct):
235 239 return all([self.subrule_check(r_conf, struct) for r_conf
236 240 in self.rules])
237 241
238 242
243 class NOT(Rule):
244 def __init__(self, rules, *args, **kwargs):
245 super(NOT, self).__init__({}, *args, **kwargs)
246 self.rules = rules
247
248 def match(self, struct):
249 return all([not self.subrule_check(r_conf, struct) for r_conf
250 in self.rules])
251
252
239 253 class OR(Rule):
240 254 def __init__(self, rules, *args, **kwargs):
241 255 super(OR, self).__init__({}, *args, **kwargs)
242 256 self.rules = rules
243 257
244 258 def match(self, struct):
245 259 return any([self.subrule_check(r_conf, struct) for r_conf
246 260 in self.rules])
247 261
248 262
249 263 class RuleService(object):
250 264 @staticmethod
251 265 def rule_from_config(config, field_mappings, labels_dict,
252 266 manipulator_func=None):
253 267 """
254 268 Returns modified rule with manipulator function
255 269 By default manipulator function replaces field id from labels_dict
256 270 with current field id proper for the rule from fields_mappings
257 271
258 272 because label X_X id might be pointing different value on next request
259 273 when new term is returned from elasticsearch - this ensures things
260 274 are kept 1:1 all the time
261 275 """
262 276 rev_map = {}
263 277 for k, v in labels_dict.items():
264 278 rev_map[(v['agg'], v['key'],)] = k
265 279
266 280 if manipulator_func is None:
267 281 def label_rewriter_func(rule):
268 282 field = rule.config.get('field')
269 if not field or rule.config['field'] in ['__OR__', '__AND__']:
283 if not field or rule.config['field'] in ['__OR__',
284 '__AND__', '__NOT__']:
270 285 return
271 286
272 287 to_map = field_mappings.get(rule.config['field'])
273 288
274 289 # we need to replace series field with _AE_NOT_FOUND_ to not match
275 290 # accidently some other field which happens to have the series that
276 291 # was used when the alert was created
277 292 if to_map:
278 293 to_replace = rev_map.get((to_map['agg'], to_map['key'],),
279 294 '_AE_NOT_FOUND_')
280 295 else:
281 296 to_replace = '_AE_NOT_FOUND_'
282 297
283 298 rule.config['field'] = to_replace
284 299 rule.type_matrix[to_replace] = {"type": 'float'}
285 300
286 301 manipulator_func = label_rewriter_func
287 302
288 303 return Rule(config, {}, config_manipulator=manipulator_func)
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now