Skip to content

Commit

Permalink
Closes #261 - update overall state even if state do not change
Browse files Browse the repository at this point in the history
Compute overall host/service state on insertion
Fix broken tests
  • Loading branch information
mohierf committed Jan 28, 2017
1 parent 16405a7 commit 0e0196e
Show file tree
Hide file tree
Showing 8 changed files with 342 additions and 36 deletions.
2 changes: 1 addition & 1 deletion alignak_backend/__init__.py
Expand Up @@ -8,7 +8,7 @@
This module is an Alignak REST backend
"""
# Application version and manifest
VERSION = (0, 8, 4)
VERSION = (0, 8, 5, 1)

__application__ = u"Alignak_Backend"
__short_version__ = '.'.join((str(each) for each in VERSION[:2]))
Expand Down
88 changes: 77 additions & 11 deletions alignak_backend/app.py
Expand Up @@ -1080,6 +1080,40 @@ def pre_host_patch(updates, original):
del updates['_updated']


def after_insert_host(items):
"""
Hook after host inserted.
:param items: host fields
:type items: dict
:return: None
"""
for dummy, item in enumerate(items):
overall_state = 0

acknowledged = item['ls_acknowledged']
downtimed = item['ls_downtimed']
state = item['ls_state']
state = state.upper()

if acknowledged:
overall_state = 1
elif downtimed:
overall_state = 2
else:
if state == 'UNREACHABLE':
overall_state = 3
elif state == 'DOWN':
overall_state = 4

# Do not care about services... when inserting an host,
# services are not yet existing for this host!

# Host overall was computed, update the host overall state
lookup = {"_id": item['_id']}
patch_internal('host', {"_overall_state_id": overall_state}, False, False, **lookup)


def pre_service_patch(updates, original):
"""
Hook before updating a service element.
Expand Down Expand Up @@ -1110,9 +1144,9 @@ def pre_service_patch(updates, original):
:type original: dict
:return: None
"""

if '_overall_state_id' in updates:
abort(make_response("Updating _overall_state_id for a service is forbidden", 412))
# Allow update because it must be done when inserting a service
# if '_overall_state_id' in updates:
# abort(make_response("Updating _overall_state_id for a service is forbidden", 412))

for key in updates:
if key not in ['_overall_state_id', '_updated', '_realm'] and not key.startswith('ls_'):
Expand All @@ -1121,11 +1155,7 @@ def pre_service_patch(updates, original):
# pylint: disable=too-many-boolean-expressions
if 'ls_state_type' in updates and updates['ls_state_type'] == 'HARD':
# We updated the service live state, compute the new overall state
if ('ls_state' in updates and updates['ls_state'] != original['ls_state']) or \
('ls_acknowledged' in updates and
updates['ls_acknowledged'] != original['ls_acknowledged']) or \
('ls_downtimed' in updates and
updates['ls_downtimed'] != original['ls_downtimed']):
if 'ls_state' in updates or 'ls_acknowledged' in updates or 'ls_downtimed' in updates:
overall_state = 0

acknowledged = original['ls_acknowledged']
Expand Down Expand Up @@ -1161,6 +1191,41 @@ def pre_service_patch(updates, original):
del updates['_updated']


def after_insert_service(items):
"""
Hook after service inserted.
:param items: host fields
:type items: dict
:return: None
"""
for dummy, item in enumerate(items):
overall_state = 0

acknowledged = item['ls_acknowledged']
downtimed = item['ls_downtimed']
state = item['ls_state']
state = state.upper()

if acknowledged:
overall_state = 1
elif downtimed:
overall_state = 2
else:
if state == 'WARNING':
overall_state = 3
elif state == 'CRITICAL':
overall_state = 4
elif state == 'UNKNOWN':
overall_state = 3
elif state == 'UNREACHABLE':
overall_state = 4

# Service overall was computed, update the service overall state
lookup = {"_id": item['_id']}
patch_internal('service', {"_overall_state_id": overall_state}, False, False, **lookup)


def after_updated_service(updated, original):
"""
Hook called after a service got updated
Expand All @@ -1171,9 +1236,8 @@ def after_updated_service(updated, original):
:type original: dict
:return: None
"""
if '_overall_state_id' in updated and \
updated['_overall_state_id'] != original['_overall_state_id']:
# Service overall state changed, we should update its host overall state
if '_overall_state_id' in updated:
# Service overall was updated, we should update its host overall state
lookup = {"_id": original['host']}
patch_internal('host', {"_overall_state_id": -1}, False, False, **lookup)

Expand Down Expand Up @@ -1416,6 +1480,8 @@ def get_settings(prev_settings):
app.on_pre_GET += pre_get
app.on_insert_user += pre_user_post
app.on_update_user += pre_user_patch
app.on_inserted_host += after_insert_host
app.on_inserted_service += after_insert_service
app.on_update_host += pre_host_patch
app.on_update_service += pre_service_patch
app.on_updated_service += after_updated_service
Expand Down
2 changes: 2 additions & 0 deletions alignak_backend/grafana.py
Expand Up @@ -152,6 +152,8 @@ def build_target(self, item, fields):
my_target = my_target.replace("/", "-")
# space becomes a _
my_target = my_target.replace(" ", "_")
# % becomes _pct
my_target = my_target.replace("%", "_pct")
# all character not in [a-zA-Z_-0-9.] is removed
my_target = re.sub(r'[^a-zA-Z_\-0-9\.\$]', '', my_target)

Expand Down
31 changes: 19 additions & 12 deletions alignak_backend/perfdata.py
Expand Up @@ -63,7 +63,6 @@ def __init__(self, string):
self.name = self.value = self.uom = \
self.warning = self.critical = self.min = self.max = None
string = string.strip()
# print "Analysis string", string
matches = METRIC_PATTERN.match(string)
if matches:
# Get the name but remove all ' in it
Expand All @@ -74,20 +73,28 @@ def __init__(self, string):
self.critical = guess_int_or_float(matches.group(5))
self.min = guess_int_or_float(matches.group(6))
self.max = guess_int_or_float(matches.group(7))
# print 'Name', self.name
# print "Value", self.value
# print "Res", r
# print r.groups()
if self.uom == '%':
self.min = 0
self.max = 100

def __str__(self):
def __str__(self): # pragma: no cover, only for debugging purpose
string = "%s=%s%s" % (self.name, self.value, self.uom)
if self.warning:
if self.warning is not None:
string += ";%s" % (self.warning)
if self.critical:
else:
string += ";"
if self.critical is not None:
string += ";%s" % (self.critical)
else:
string += ";"
if self.min is not None:
string += ";%s" % (self.min)
else:
string += ";"
if self.max is not None:
string += ";%s" % (self.max)
else:
string += ";"
return string


Expand All @@ -106,14 +113,14 @@ def __init__(self, string):
if metric.name is not None:
self.metrics[metric.name] = metric

def __iter__(self):
def __iter__(self): # pragma: no cover, not used internally
return self.metrics.itervalues()

def __len__(self):
def __len__(self): # pragma: no cover, not used internally
return len(self.metrics)

def __getitem__(self, key):
def __getitem__(self, key): # pragma: no cover, not used internally
return self.metrics[key]

def __contains__(self, key):
def __contains__(self, key): # pragma: no cover, not used internally
return key in self.metrics
32 changes: 31 additions & 1 deletion alignak_backend/timeseries.py
Expand Up @@ -77,7 +77,21 @@ def prepare_data(item):
m = re.search(r'^(.*)\.[\d]{10}$', fields['name'])
if m:
fields['name'] = m.group(1)
fields['name'] = fields['name'].replace(" ", "_")

# Sanitize field name for TSDB (Graphite or Influx):
# + becomes a _
my_target = fields['name'].replace("+", "_")
# / becomes a -
my_target = my_target.replace("/", "-")
# space becomes a _
my_target = my_target.replace(" ", "_")
# % becomes _pct
my_target = my_target.replace("%", "_pct")
# all character not in [a-zA-Z_-0-9.] is removed
my_target = re.sub(r'[^a-zA-Z_\-0-9\.\$]', '', my_target)
if fields['name'] != my_target:
print("Sanitized %s to %s" % (fields['name'], my_target))
fields['name'] = my_target

if fields['value'] is not None:
data_timeseries['data'].append(
Expand All @@ -103,6 +117,22 @@ def prepare_data(item):
'uom': fields['uom']
}
)
if fields['min'] is not None:
data_timeseries['data'].append(
{
'name': fields['name'] + '_min',
'value': fields['min'],
'uom': fields['uom']
}
)
if fields['max'] is not None:
data_timeseries['data'].append(
{
'name': fields['name'] + '_max',
'value': fields['max'],
'uom': fields['uom']
}
)
return data_timeseries

@staticmethod
Expand Down
11 changes: 7 additions & 4 deletions test/test_hook_template.py
Expand Up @@ -135,7 +135,7 @@ def test_host_templates(self):

schema = host_schema()
template_fields = {}
ignore_fields = ['name', 'realm', '_realm', '_template_fields',
ignore_fields = ['name', 'realm', '_realm', '_overall_state_id', '_template_fields',
'_templates', '_is_template',
'_templates_with_services']
for key in schema['schema']:
Expand Down Expand Up @@ -743,12 +743,15 @@ def test_host_services_template(self):
self.assertTrue(rs[12]['_is_template'])

# Now delete a template service
response = requests.get(self.endpoint + '/service/' + ret_new['_id'],
params=sort_id, auth=self.auth)
response = response.json()
headers_delete = {
'Content-Type': 'application/json',
'If-Match': ret_new['_etag']
'If-Match': response['_etag']
}
requests.delete(self.endpoint + '/service/' + ret_new['_id'], headers=headers_delete,
auth=self.auth)
requests.delete(self.endpoint + '/service/' + response['_id'],
headers=headers_delete, auth=self.auth)
response = requests.get(self.endpoint + '/service', params=sort_id, auth=self.auth)
resp = response.json()
service_name = []
Expand Down
27 changes: 22 additions & 5 deletions test/test_overall_state.py
Expand Up @@ -109,24 +109,41 @@ def test_update_host(self):
del data['realm']
data['_realm'] = self.realm_all
requests.post(self.endpoint + '/host', json=data, headers=headers, auth=self.auth)
response = requests.get(self.endpoint + '/host', params=sort_id, auth=self.auth)
params = {'sort': '_id', 'where': json.dumps({'_is_template': True})}
response = requests.get(self.endpoint + '/host', params=params, auth=self.auth)
resp = response.json()
self.assertEqual(len(resp['_items']), 2)
self.assertEqual(len(resp['_items']), 1)
rh = resp['_items']
# On host insertion, _overall_state_id field is 3, because host is UNREACHABLE
self.assertEqual(3, rh[0]['_overall_state_id'])

# Add service 1
data = json.loads(open('cfg/service_srv001_ping.json').read())
data['host'] = rh[1]['_id']
data['host'] = rh[0]['_id']
data['check_command'] = rc[2]['_id']
data['_realm'] = self.realm_all
requests.post(self.endpoint + '/service', json=data, headers=headers, auth=self.auth)
response = response.json()
response = requests.get(
self.endpoint + '/service/' + response['_id'], params=sort_id, auth=self.auth
)
ls_service = response.json()
# On service insertion, _overall_state_id field is 3, because service is UNKNOWN
self.assertEqual(3, ls_service['_overall_state_id'])

# Add service 2
data = json.loads(open('cfg/service_srv002_ping.json').read())
data['host'] = rh[1]['_id']
data['host'] = rh[0]['_id']
data['check_command'] = rc[2]['_id']
data['_realm'] = self.realm_all
requests.post(self.endpoint + '/service', json=data, headers=headers, auth=self.auth)
response = response.json()
response = requests.get(
self.endpoint + '/service/' + response['_id'], params=sort_id, auth=self.auth
)
ls_service = response.json()
# On service insertion, _overall_state_id field is 3, because service is UNKNOWN
self.assertEqual(3, ls_service['_overall_state_id'])

# Get all services
response = requests.get(self.endpoint + '/service', params=sort_id, auth=self.auth)
Expand All @@ -152,7 +169,7 @@ def test_update_host(self):
self.endpoint + '/service/' + service['_id'], params=sort_id, auth=self.auth
)
ls_service = response.json()
# _overall_state_id field is 0
# On service insertion, _overall_state_id field is 3, because service is UNKNOWN
self.assertEqual(0, ls_service['_overall_state_id'])

# Get host
Expand Down

0 comments on commit 0e0196e

Please sign in to comment.