def test_2_post_exceptions(self): """ Test post a user with errors (so exceptions) :return: None """ backend = Backend(self.backend_address) backend.login('admin', 'admin') # Create a new user, bad parameters # Mandatory field user_name is missing ... data = { "name": "Testing user", "alias": "Fred", "back_role_super_admin": False, "back_role_admin": [], "min_business_impact": 0, } with assert_raises(BackendException) as cm: backend.post('user', data=data) ex = cm.exception print('exception:', str(ex.code), ex.message, ex.response) if "_issues" in ex.response: for issue in ex.response["_issues"]: print("Issue: %s - %s" % (issue, ex.response["_issues"][issue])) assert_true(ex.code == 422) assert_true(ex.response["_issues"])
def test_multiprocess(self): """ Test multiprocess get right all elements :return: None """ print('') print('test creation') print('Create client API for URL:', self.backend_address) backend = Backend(self.backend_address, 8) backend.login('admin', 'admin') items = backend.get('realm') realm_id = items['_items'][0]['_id'] # add 2000 commands backend.delete("command", {}) data = {'command_line': 'check_ping', '_realm': realm_id} for i in range(1, 2001): data['name'] = "cmd %d" % i backend.post('command', data) # get without multiprocess backend_yannsolo = Backend(self.backend_address) backend_yannsolo.login('admin', 'admin') start_time = time.time() resp = backend_yannsolo.get_all('command', {'max_results': 20}) threads_1 = time.time() - start_time self.assertEqual(len(resp['_items']), 2002, "Number of commands in non multiprocess mode") # get with multiprocess (8 processes) start_time = time.time() resp = backend.get_all('command', {'max_results': 20}) threads_8 = time.time() - start_time self.assertEqual(len(resp['_items']), 2002, "Number of commands in multiprocess mode") ids = [] for dat in resp['_items']: ids.append(dat['_id']) self.assertEqual(len(ids), 2002, "Number of id") # remove duplicates ids_final = set(ids) self.assertEqual(len(ids_final), 2002, "Number of id unique") print(threads_1) print(threads_8)
def test_1_post_successful(self): """ Test post a timeperiod successfully :return: None """ backend = Backend(self.backend_address) backend.login('admin', 'admin') # Create a new timeperiod print('create a timeperiod') data = { "name": "Testing TP", "alias": "Test TP", "dateranges": [ {u'monday': u'09:00-17:00'}, {u'tuesday': u'09:00-17:00'}, {u'wednesday': u'09:00-17:00'}, {u'thursday': u'09:00-17:00'}, {u'friday': u'09:00-17:00'} ], "_realm": self.realmAll_id } response = backend.post('timeperiod', data=data) assert_true('_created' in response) assert_true('_updated' in response) assert_true(response['_created'] == response['_updated'])
def test_2_delete_exceptions(self): """ Test delete a timeperiod with errors (so exceptions) :return: None """ backend = Backend(self.backend_address) backend.login("admin", "admin") # Create a new timeperiod data = { "name": "Testing TP", "alias": "Test TP", "dateranges": [ {u"monday": u"09:00-17:00"}, {u"tuesday": u"09:00-17:00"}, {u"wednesday": u"09:00-17:00"}, {u"thursday": u"09:00-17:00"}, {u"friday": u"09:00-17:00"}, ], "_realm": self.realmAll_id, } response = backend.post("timeperiod", data=data) assert_true(response["_status"] == "OK") timeperiod_id = response["_id"] timeperiod_etag = response["_etag"] with assert_raises(BackendException) as cm: headers = {"If-Match": timeperiod_etag} response = backend.delete("/".join(["timeperiod", "5" + timeperiod_id]), headers) ex = cm.exception print("exception:", str(ex.code)) assert_true(ex.code == 1003, str(ex))
def test_1_delete_successful(self): """ Test delete a timeperiod successfully :return: None """ backend = Backend(self.backend_address) backend.login("admin", "admin") # Create a new timeperiod data = { "name": "Testing TP", "alias": "Test TP", "dateranges": [ {u"monday": u"09:00-17:00"}, {u"tuesday": u"09:00-17:00"}, {u"wednesday": u"09:00-17:00"}, {u"thursday": u"09:00-17:00"}, {u"friday": u"09:00-17:00"}, ], "_realm": self.realmAll_id, } response = backend.post("timeperiod", data=data) assert_true(response["_status"] == "OK") timeperiod_id = response["_id"] timeperiod_etag = response["_etag"] headers = {"If-Match": timeperiod_etag} response = backend.delete("/".join(["timeperiod", timeperiod_id]), headers=headers) assert_true(response["_status"] == "OK")
def test_multiprocess(self): """ Test multiprocess get right all elements :return: None """ print('') print('test creation') print('Create client API for URL:', self.backend_address) backend = Backend(self.backend_address, 8) backend.login('admin', 'admin') items = backend.get('realm') realm_id = items['_items'][0]['_id'] # add 700 commands backend.delete("command", {}) data = {'command_line': 'check_ping', '_realm': realm_id} for i in range(1, 2001): data['name'] = "cmd %d" % i backend.post('command', data) # get without multiprocess backend_yannsolo = Backend(self.backend_address) backend_yannsolo.login('admin', 'admin') start_time = time.time() resp = backend_yannsolo.get_all('command', {'max_results': 20}) threads_1 = time.time() - start_time self.assertEqual(len(resp['_items']), 2000, "Number of commands in non multiprocess mode") # get with multiprocess (8 processes) start_time = time.time() resp = backend.get_all('command', {'max_results': 20}) threads_8 = time.time() - start_time self.assertEqual(len(resp['_items']), 2000, "Number of commands in multiprocess mode") ids = [] for dat in resp['_items']: ids.append(dat['_id']) self.assertEqual(len(ids), 2000, "Number of id") # remove doubles ids_final = set(ids) self.assertEqual(len(ids_final), 2000, "Number of id unique") print(threads_1) print(threads_8)
def test_3_delete_connection_error(self): """ Backend connection error when deleting an object... :return: None """ print('test connection error when deleting an object') # Create client API backend = Backend(self.backend_address) backend.login('admin', 'admin') # Create a new timeperiod data = { "name": "Testing TP 2", "alias": "Test TP 2", "dateranges": [{ u'monday': u'09:00-17:00' }, { u'tuesday': u'09:00-17:00' }, { u'wednesday': u'09:00-17:00' }, { u'thursday': u'09:00-17:00' }, { u'friday': u'09:00-17:00' }], "_realm": self.realmAll_id } response = backend.post('timeperiod', data=data) assert_true(response['_status'] == 'OK') timeperiod_id = response['_id'] timeperiod_etag = response['_etag'] headers = {'If-Match': timeperiod_etag} response = backend.delete('/'.join(['timeperiod', timeperiod_id]), headers=headers) assert_true(response['_status'] == 'OK') print("stop the alignak backend") self.pid.kill() with assert_raises(BackendException) as cm: headers = {'If-Match': timeperiod_etag} response = backend.delete('/'.join(['timeperiod', timeperiod_id]), headers=headers) assert_true(response['_status'] == 'OK') ex = cm.exception self.assertEqual(ex.code, 1000)
def test_2_delete_exceptions(self): """ Test delete a timeperiod with errors (so exceptions) :return: None """ backend = Backend(self.backend_address) backend.login('admin', 'admin') # Create a new timeperiod data = { "name": "Testing TP", "alias": "Test TP", "dateranges": [{ u'monday': u'09:00-17:00' }, { u'tuesday': u'09:00-17:00' }, { u'wednesday': u'09:00-17:00' }, { u'thursday': u'09:00-17:00' }, { u'friday': u'09:00-17:00' }], "_realm": self.realmAll_id } response = backend.post('timeperiod', data=data) assert_true(response['_status'] == 'OK') timeperiod_id = response['_id'] timeperiod_etag = response['_etag'] with assert_raises(BackendException) as cm: headers = {'If-Match': timeperiod_etag} response = backend.delete( '/'.join(['timeperiod', '5' + timeperiod_id]), headers) ex = cm.exception print('exception:', str(ex.code)) assert_true(ex.code == 404, str(ex))
def test_1_delete_successful(self): """ Test delete a timeperiod successfully :return: None """ backend = Backend(self.backend_address) backend.login('admin', 'admin') # Create a new timeperiod data = { "name": "Testing TP", "alias": "Test TP", "dateranges": [{ u'monday': u'09:00-17:00' }, { u'tuesday': u'09:00-17:00' }, { u'wednesday': u'09:00-17:00' }, { u'thursday': u'09:00-17:00' }, { u'friday': u'09:00-17:00' }], "_realm": self.realmAll_id } response = backend.post('timeperiod', data=data) assert_true(response['_status'] == 'OK') timeperiod_id = response['_id'] timeperiod_etag = response['_etag'] headers = {'If-Match': timeperiod_etag} response = backend.delete('/'.join(['timeperiod', timeperiod_id]), headers=headers) assert_true(response['_status'] == 'OK')
def test_22_post_patch_delete(self): global backend_address print '' print 'post/delete/patch some hostgroups' # Create client API backend = Backend(backend_address) print 'Login ...' print 'authenticated:', backend.authenticated result = backend.login('admin', 'admin') print 'authenticated:', backend.authenticated print 'token:', backend.token assert_true(backend.authenticated) # Get all hostgroups print 'get all hostgroups at once' items = backend.get_all('hostgroup') print "Got %d elements:" % len(items) assert_true('_items' not in items) for item in items: assert_true('hostgroup_name' in item) assert_true('_id' in item) assert_true('_etag' in item) print "Group: ", item['hostgroup_name'], item['_id'] # Test contact still exists ... delete him! if item['hostgroup_name'] == 'test': headers = { 'If-Match': item['_etag'] } response = backend.delete('/'.join(['hostgroup', item['_id']]), headers) print "Response:", response # Create a new hostgroup, bad parameters print 'create a hostgroup, missing fields' # Mandatory field hostgroup_name is missing ... data = { "name": "Testing hostgroup", "alias": "Fred", "back_role_super_admin": False, "back_role_admin": [], "min_business_impact": 0, } with assert_raises(BackendException) as cm: response = backend.post('hostgroup', data=data) ex = cm.exception print 'exception:', str(ex.code), ex.message, ex.response if "_issues" in ex.response: for issue in ex.response["_issues"]: print "Issue: %s - %s" %(issue, ex.response["_issues"][issue]) assert_true(ex.code == 422) assert_true(ex.response["_issues"]) # Create a new hostgroup print 'create a hostgroup' data = { "hostgroup_name": "test", "name": "Testing hostgroup", "alias": "Fred", "note": "Hostgroup note ...", "realm": "all" } response = backend.post('hostgroup', data=data) print "Response:", response assert_true('_created' in response) assert_true('_updated' in response) assert_true(response['_created'] == response['_updated']) # Get all hostgroups print 'get all hostgroups at once' # Filter the templates ... items = backend.get_all('hostgroup') print "Got %d elements:" % len(items) assert_true('_items' not in items) assert_true(len(items) > 0) # Search test hostgroup hostgroup_id = '' hostgroup_etag = '' for item in items: assert_true('hostgroup_name' in item) print "hostgroup: ", item['hostgroup_name'] if item['hostgroup_name'] == 'test': hostgroup_id = item['_id'] hostgroup_etag = item['_etag'] assert_true(hostgroup_id != '') assert_true(hostgroup_etag != '') print 'changing hostgroup alias ... no _etag' print 'id:', hostgroup_id print 'etag:', hostgroup_etag with assert_raises(BackendException) as cm: data = {'alias': 'modified with no header'} # headers['If-Match'] = hostgroup_etag response = backend.patch('/'.join(['hostgroup', hostgroup_id]), data=data) ex = cm.exception print 'exception:', str(ex.code) assert_true(ex.code == 1005, str(ex)) print 'changing hostgroup alias ...' print 'id:', hostgroup_id print 'etag:', hostgroup_etag data = {'alias': 'modified test'} headers = {'If-Match': hostgroup_etag} response = backend.patch('/'.join(['hostgroup', hostgroup_id]), data=data, headers=headers) print 'response:', response assert_true(response['_status'] == 'OK') response = backend.get('/'.join(['hostgroup', hostgroup_id])) print 'response:', response assert_true(response['alias'] == 'modified test') print 'changing hostgroup alias ... bad _etag (inception = True)' print 'id:', hostgroup_id print 'etag:', hostgroup_etag data = {'alias': 'modified test again'} headers = {'If-Match': hostgroup_etag} response = backend.patch('/'.join(['hostgroup', hostgroup_id]), data=data, headers=headers, inception=True) print 'response:', response assert_true(response['_status'] == 'OK') response = backend.get('/'.join(['hostgroup', hostgroup_id])) print 'response:', response assert_true(response['alias'] == 'modified test again') print 'changing hostgroup alias ... bad _etag (inception = False)' print 'id:', hostgroup_id print 'etag:', hostgroup_etag with assert_raises(BackendException) as cm: data = {'alias': 'modified test again and again'} headers = {'If-Match': hostgroup_etag} response = backend.patch('/'.join(['hostgroup', hostgroup_id]), data=data, headers=headers) ex = cm.exception print 'exception:', str(ex.code) assert_true(ex.code == 412, str(ex)) response = backend.get('/'.join(['hostgroup', hostgroup_id])) print 'response:', response # Not changed ! assert_true(response['alias'] == 'modified test again') response = backend.get('/'.join(['hostgroup', hostgroup_id])) print 'response:', response # Not changed ! assert_true(response['alias'] == 'modified test again') print 'deleting hostgroup ... bad href' with assert_raises(BackendException) as cm: headers = { 'If-Match': item['_etag'] } response = backend.delete('/'.join(['hostgroup', '5'+item['_id']]), headers) print "Response:", response ex = cm.exception print 'exception:', str(ex.code) assert_true(ex.code == 1003, str(ex))
def test_21_post_patch_delete(self): global backend_address print '' print 'post/delete/patch some elements' # Create client API backend = Backend(backend_address) print 'Login ...' print 'authenticated:', backend.authenticated result = backend.login('admin', 'admin') print 'authenticated:', backend.authenticated print 'token:', backend.token assert_true(backend.authenticated) # Get all contacts print 'get all contacts at once' parameters = { 'where': '{"register":true}' } items = backend.get_all('contact', params=parameters) print "Got %d elements:" % len(items) assert_true('_items' not in items) for item in items: assert_true('contact_name' in item) assert_true('_id' in item) assert_true('_etag' in item) print "Contact: ", item['contact_name'], item['_id'] # Test contact still exists ... delete him! if item['contact_name'] == 'test': headers = { 'If-Match': item['_etag'] } response = backend.delete('/'.join(['contact', item['_id']]), headers) print "Response:", response # Get all timeperiods print 'get all timeperiods at once' parameters = { 'where': '{"register":true}' } items = backend.get_all('timeperiod', params=parameters) print "Got %d elements:" % len(items) assert_true('_items' not in items) tp_id = '' for item in items: assert_true('timeperiod_name' in item) assert_true('_id' in item) tp_id = item['_id'] print item print "TP: %s (%s), id=%s" % (item['timeperiod_name'], item['name'], item['_id']) if not tp_id: # Create a new timeperiod print 'create a timeperiod' data = { "timeperiod_name": "test", "name": "Testing TP", "alias": "Test TP", "dateranges": [ {u'monday': u'09:00-17:00'}, {u'tuesday': u'09:00-17:00'}, {u'wednesday': u'09:00-17:00'}, {u'thursday': u'09:00-17:00'}, {u'friday': u'09:00-17:00'} ], "register": True } response = backend.post('timeperiod', data=data) print "Response:", response assert_true('_created' in response) assert_true('_updated' in response) assert_true(response['_created'] == response['_updated']) # Get all timeperiods print 'get all timeperiods at once' parameters = { 'where': '{"register":true}' } items = backend.get_all('timeperiod', params=parameters) print "Got %d elements:" % len(items) assert_true('_items' not in items) tp_id = '' for item in items: assert_true('timeperiod_name' in item) assert_true('_id' in item) tp_id = item['_id'] print "TP: %s (%s), id=%s" % (item['timeperiod_name'], item['name'], item['_id']) assert_true(tp_id != '') # Create a new contact, bad parameters print 'create a contact, missing fields' # Mandatory field contact_name is missing ... data = { "name": "Testing contact", "alias": "Fred", "back_role_super_admin": False, "back_role_admin": [], "min_business_impact": 0, } with assert_raises(BackendException) as cm: response = backend.post('contact', data=data) ex = cm.exception print 'exception:', str(ex.code), ex.message, ex.response if "_issues" in ex.response: for issue in ex.response["_issues"]: print "Issue: %s - %s" %(issue, ex.response["_issues"][issue]) assert_true(ex.code == 422) assert_true(ex.response["_issues"]) # Create a new contact print 'create a contact' data = { "contact_name": "test", "name": "Testing contact", "alias": "Fred", "back_role_super_admin": False, "back_role_admin": [], "min_business_impact": 0, "email": "*****@*****.**", "is_admin": False, "expert": False, "can_submit_commands": False, "host_notifications_enabled": True, "host_notification_period": tp_id, "host_notification_commands": [ ], "host_notification_options": [ "d", "u", "r" ], "service_notifications_enabled": True, "service_notification_period": tp_id, "service_notification_commands": [ ], "service_notification_options": [ "w", "u", "c", "r" ], "retain_status_information": False, "note": "Monitoring template : default", "retain_nonstatus_information": False, "definition_order": 100, "address1": "", "address2": "", "address3": "", "address4": "", "address5": "", "address6": "", "pager": "", "notificationways": [], "register": True } response = backend.post('contact', data=data) print "Response:", response assert_true('_created' in response) assert_true('_updated' in response) assert_true(response['_created'] == response['_updated']) # Get all contacts print 'get all contacts at once' # Filter the templates ... parameters = { 'where': '{"register":true}' } items = backend.get_all('contact', params=parameters) print "Got %d elements:" % len(items) assert_true('_items' not in items) assert_true(len(items) > 0) # Search test contact contact_id = '' contact_etag = '' for item in items: assert_true('contact_name' in item) print "Contact: ", item['contact_name'] if item['contact_name'] == 'test': contact_id = item['_id'] contact_etag = item['_etag'] assert_true(contact_id != '') assert_true(contact_etag != '') print 'changing contact alias ... no _etag' print 'id:', contact_id print 'etag:', contact_etag with assert_raises(BackendException) as cm: data = {'alias': 'modified with no header'} # headers['If-Match'] = contact_etag response = backend.patch('/'.join(['contact', contact_id]), data=data) ex = cm.exception print 'exception:', str(ex.code) assert_true(ex.code == 1005, str(ex)) print 'changing contact alias ...' print 'id:', contact_id print 'etag:', contact_etag data = {'alias': 'modified test'} headers = {'If-Match': contact_etag} response = backend.patch('/'.join(['contact', contact_id]), data=data, headers=headers) print 'response:', response assert_true(response['_status'] == 'OK') response = backend.get('/'.join(['contact', contact_id])) print 'response:', response assert_true(response['alias'] == 'modified test') print 'changing contact alias ... bad _etag (inception = True)' print 'id:', contact_id print 'etag:', contact_etag data = {'alias': 'modified test again'} headers = {'If-Match': contact_etag} response = backend.patch('/'.join(['contact', contact_id]), data=data, headers=headers, inception=True) print 'response:', response assert_true(response['_status'] == 'OK') response = backend.get('/'.join(['contact', contact_id])) print 'response:', response assert_true(response['alias'] == 'modified test again') print 'changing contact unknown field ... must be refused' print 'id:', contact_id print 'etag:', contact_etag with assert_raises(BackendException) as cm: data = {'bad_field': 'bad field name ... unknown in data model'} headers = {'If-Match': contact_etag} response = backend.patch('/'.join(['contact', contact_id]), data=data, headers=headers, inception=True) ex = cm.exception print 'exception:', str(ex.code), ex.message, ex.response if "_issues" in ex.response: for issue in ex.response["_issues"]: print "Issue: %s - %s" %(issue, ex.response["_issues"][issue]) assert_true(ex.code == 422) assert_true(ex.response["_issues"]) print 'changing contact alias ... bad _etag (inception = False)' print 'id:', contact_id print 'etag:', contact_etag with assert_raises(BackendException) as cm: data = {'alias': 'modified test again and again'} headers = {'If-Match': contact_etag} response = backend.patch('/'.join(['contact', contact_id]), data=data, headers=headers) ex = cm.exception print 'exception:', str(ex.code) assert_true(ex.code == 412, str(ex)) response = backend.get('/'.join(['contact', contact_id])) print 'response:', response # Not changed ! assert_true(response['alias'] == 'modified test again') response = backend.get('/'.join(['contact', contact_id])) print 'response:', response # Not changed ! assert_true(response['alias'] == 'modified test again') print 'deleting contact ... bad href' with assert_raises(BackendException) as cm: headers = { 'If-Match': item['_etag'] } response = backend.delete('/'.join(['contact', '5'+item['_id']]), headers) print "Response:", response ex = cm.exception print 'exception:', str(ex.code) assert_true(ex.code == 1003, str(ex))
def test_04_connection_username(self): global backend_address print '' print 'test accepted connection with username/password' # Create client API backend = Backend(backend_address) print 'Login ...' result = backend.login('admin', 'admin') print 'authenticated:', backend.authenticated print 'token:', backend.token assert_true(backend.authenticated) print 'Logout ...' result = backend.logout() print 'authenticated:', backend.authenticated print 'token:', backend.token assert_false(backend.authenticated) print 'Login ...' print 'authenticated:', backend.authenticated result = backend.login('admin', 'admin') print 'authenticated:', backend.authenticated print 'token:', backend.token assert_true(backend.authenticated) print 'Logout ...' result = backend.logout() print 'authenticated:', backend.authenticated print 'token:', backend.token assert_false(backend.authenticated) print 'Logout ...' result = backend.logout() print 'authenticated:', backend.authenticated print 'token:', backend.token assert_false(backend.authenticated) print 'get object ... must be refused!' with assert_raises(BackendException) as cm: items = backend.get('host') ex = cm.exception print 'exception:', str(ex.code) assert_true(ex.code == 1001, str(ex)) print 'get_all object ... must be refused!' with assert_raises(BackendException) as cm: items = backend.get_all('host') ex = cm.exception print 'exception:', str(ex.code) assert_true(ex.code == 1001, str(ex)) print 'get all domains ... must be refused!' with assert_raises(BackendException) as cm: items = backend.get_domains() ex = cm.exception print 'exception:', str(ex.code) assert_true(ex.code == 1001, str(ex)) print 'post data ... must be refused!' with assert_raises(BackendException) as cm: data = { 'fake': 'fake' } response = backend.post('contact', data=data) ex = cm.exception print 'exception:', str(ex.code) assert_true(ex.code == 1001, str(ex)) print 'patch data ... must be refused!' with assert_raises(BackendException) as cm: data = { 'fake': 'fake' } headers = { 'If-Match': '' } response = backend.patch('contact', data=data, headers=headers) ex = cm.exception print 'exception:', str(ex.code) assert_true(ex.code == 1001, str(ex)) print 'delete data ... must be refused!' with assert_raises(BackendException) as cm: data = { 'fake': 'fake' } headers = { 'If-Match': '' } response = backend.delete('contact', headers=headers) ex = cm.exception print 'exception:', str(ex.code) assert_true(ex.code == 1001, str(ex))
class __BackendConnection(object): """ Base class for all objects state management (displayed icon, ...) """ def __init__(self, backend_endpoint='http://127.0.0.1:5002'): self.backend_endpoint = backend_endpoint self.backend = Backend(backend_endpoint) self.connected = False def login(self, username, password=None): """ Log into the backend If password is provided, use the backend login function to authenticate the user If no password is provided, the username is assumed to be an authentication token and we use the backend connect function. """ logger.info("login, connection requested, login: %s", username) self.connected = False if not username: # pragma: no cover, should not happen # Refuse backend login without username logger.warning("No login without username!") return self.connected if not password: # pragma: no cover, should not happen # Set backend token (no login request). logger.debug("Update backend token") self.backend.token = username self.connected = True return self.connected try: # Backend real login logger.info("Requesting backend authentication, username: %s", username) self.connected = self.backend.login(username, password) except BackendException: # pragma: no cover, should not happen logger.warning("configured backend is not available!") except Exception as e: # pragma: no cover, should not happen logger.warning("User login exception: %s", str(e)) logger.error("traceback: %s", traceback.format_exc()) logger.info("login result: %s", self.connected) return self.connected def logout(self): """ Log out from the backend Do nothing except setting 'connected' attribute to False """ logger.info("logout") self.connected = False def count(self, object_type, params=None): """ If params is a string, it is considered to be an object id and params is modified to {'_id': params}. Else, params is used to 'get' objects from the backend. """ logger.debug("count, %s, params: %s", object_type, params) if isinstance(params, basestring): params = {'where': {'_id': params}} # Update backend search parameters if params is None: params = {'page': 0, 'max_results': 1} if 'where' in params: params['where'] = json.dumps(params['where']) if 'max_results' not in params: params['max_results'] = 1 logger.debug( "count, search in the backend for %s: parameters=%s", object_type, params ) try: result = self.backend.get(object_type, params=params) except BackendException as e: # pragma: no cover, simple protection logger.warning("count, backend exception for %s: %s", object_type, str(e)) return 0 logger.debug("count, search result for %s: result=%s", object_type, result) if not result['_status'] == 'OK': # pragma: no cover, should not happen error = [] if "content" in result: error.append(result['content']) if "_issues" in result: error.append(result['_issues']) logger.warning("count, %s: %s, not found: %s", object_type, params, error) return 0 # If more than one element is found, we get an _items list if '_items' in result: logger.debug("count, found in the backend: %s: %s", object_type, result['_items']) return result['_meta']['total'] return 0 # pragma: no cover, simple protection def get(self, object_type, params=None, all_elements=False): """ If params is a string, it is considered to be an object id and params is modified to {'_id': params}. Else, params is used to 'get' objects from the backend. Returns an object or an array of matching objects. All extra attributes (_links, _status, _meta, ...) are not returned but an '_total' attribute is added in each element to get the total count of elements stored in the backend. Returns None if the search failed. Do not raise any exception to the caller. If all_elements is True, it calls the get_all function of the backend client to get all the elements without any pagination activated. """ logger.debug("get, %s, params: %s", object_type, params) if isinstance(params, basestring): params = {'where': {'_id': params}} logger.debug("get, %s, params: %s", object_type, params) # Update backend search parameters if params is None: params = {'page': 0, 'max_results': BACKEND_PAGINATION_LIMIT} if 'where' in params: params['where'] = json.dumps(params['where']) if 'embedded' in params: params['embedded'] = json.dumps(params['embedded']) if 'where' not in params: params['where'] = {} if 'page' not in params: params['page'] = 0 if 'max_results' not in params: params['max_results'] = BACKEND_PAGINATION_LIMIT logger.debug( "get, search in the backend for %s: parameters=%s", object_type, params ) try: if all_elements: result = self.backend.get_all(object_type, params=params) else: result = self.backend.get(object_type, params=params) except BackendException as e: # pragma: no cover, simple protection logger.warning("get, backend exception for %s: %s", object_type, str(e)) return None logger.debug( "search, search result for %s: result=%s", object_type, result ) if result['_status'] != 'OK': # pragma: no cover, should not happen error = [] if "content" in result: error.append(result['content']) if "_issues" in result: error.append(result['_issues']) logger.warning("get, %s: %s, not found: %s", object_type, params, error) raise ValueError( '%s, search: %s was not found in the backend, error: %s' % ( object_type, params, error ) ) # If more than one element is found, we get an _items list if '_items' in result: if '_meta' in result: for item in result['_items']: item.update({'_total': result['_meta']['total']}) logger.debug("get, found in the backend: %s: %s", object_type, result['_items']) return result['_items'] if '_status' in result: result.pop('_status') if '_meta' in result: # result.update({'_total': result['_meta']['total']}) result['_total'] = result['_meta']['total'] logger.debug("get, found one in the backend: %s: %s", object_type, result) return result def post(self, object_type, data=None, files=None): """ Add an element """ logger.info("post, request to add a %s: data: %s", object_type, data) # Do not set header to use the client default behavior: # - set headers as {'Content-Type': 'application/json'} # - encode provided data to JSON headers = None if files: logger.info("post, request to add a %s with files: %s", object_type, files) # Set header to disable client default behavior headers = {'Content-type': 'multipart/form-data'} try: result = self.backend.post(object_type, data=data, files=files, headers=headers) logger.debug("post, response: %s", result) if result['_status'] != 'OK': logger.warning("post, error: %s", result) return None except BackendException as e: # pragma: no cover, simple protection logger.error("post, backend exception: %s", str(e)) logger.error("- response: %s", e.response) return None except Exception as e: # pragma: no cover, simple protection logger.warning("post, error: %s", str(e)) return None return result['_id'] def delete(self, object_type, object_id): """ Delete an element - object_type is the element type - object_id is the element identifier """ logger.info("delete, request to delete the %s: %s", object_type, object_id) try: # Get most recent version of the element element = self.get('/'.join([object_type, object_id])) logger.debug("delete, element: %s", element) except ValueError: # pragma: no cover, simple protection logger.warning("delete, object %s, _id=%s not found", object_type, object_id) return False try: # Request deletion headers = {'If-Match': element['_etag']} endpoint = '/'.join([object_type, object_id]) logger.info("delete, endpoint: %s", endpoint) result = self.backend.delete(endpoint, headers) logger.debug("delete, response: %s", result) if result['_status'] != 'OK': # pragma: no cover, should never happen error = [] if "content" in result: error.append(result["content"]) if "_issues" in result: error.append(result["_issues"]) for issue in result["_issues"]: error.append(result["_issues"][issue]) logger.warning("delete, error: %s", error) return False except BackendException as e: # pragma: no cover, should never happen logger.error("delete, backend exception: %s", str(e)) return False except ValueError: # pragma: no cover, should never happen logger.warning("delete, not found %s: %s", object_type, element) return False return True def update(self, object_type, object_id, data): """ Update an element - object_type is the element type - object_id is the element identifier """ logger.info("update, request to update the %s: %s", object_type, object_id) try: # Get most recent version of the element element = self.get('/'.join([object_type, object_id])) logger.debug("update, element: %s", element) except ValueError: # pragma: no cover, simple protection logger.warning("update, object %s, _id=%s not found", object_type, object_id) return False try: # Request update headers = {'If-Match': element['_etag']} endpoint = '/'.join([object_type, object_id]) logger.info("update, endpoint: %s, data: %s", endpoint, data) result = self.backend.patch(endpoint, data, headers) logger.debug("update, response: %s", result) if result['_status'] != 'OK': # pragma: no cover, should never happen error = [] if "content" in result: error.append(result["content"]) if "_issues" in result: error.append(result["_issues"]) for issue in result["_issues"]: error.append(result["_issues"][issue]) logger.warning("update, error: %s", error) return False except BackendException as e: # pragma: no cover, should never happen logger.error("update, backend exception: %s", str(e)) return False except ValueError: # pragma: no cover, should never happen logger.warning("update, not found %s: %s", object_type, element) return False return True
class DataManager(object): ''' Base class for all data manager objects ''' id = 1 """ Application data manager object """ def __init__(self, backend_endpoint='http://127.0.0.1:5000', glpi=None): """ Create an instance """ # Set a unique id for each DM object self.__class__.id += 1 # Associated backend object self.backend_endpoint = backend_endpoint self.backend = Backend(backend_endpoint) # Associated Glpi backend object self.glpi = None if glpi: self.glpi = Glpi(glpi.get('glpi_ws_backend', None)) self.glpi_ws_login = glpi.get('glpi_ws_login', None) self.glpi_ws_password = glpi.get('glpi_ws_password', None) # Backend available objects (filled with objects received from backend) # self.backend_available_objets = [] # Get known objects type from the imported modules # Search for classes including an _type attribute self.known_classes = [] for k, v in globals().items(): if isinstance(globals()[k], type) and '_type' in globals()[k].__dict__: self.known_classes.append(globals()[k]) logger.debug( "Known class %s for object type: %s", globals()[k], globals()[k].getType() ) self.connected = False self.logged_in_user = None self.connection_message = None self.loading = 0 self.loaded = False self.refresh_required = False self.refresh_done = False self.updated = datetime.utcnow() def __repr__(self): return ("<DM, id: %s, objects count: %d, user: %s, updated: %s>") % ( self.id, self.get_objects_count(), self.get_logged_user().get_username() if self.get_logged_user() else 'Not logged in', self.updated ) ## # Connected user ## def user_login(self, username, password=None, load=True): """ Set the data manager user If password is provided, use the backend login function to authenticate the user If no password is provided, the username is assumed to be an authentication token and we use the backend connect function. """ logger.info("user_login, connection requested: %s, load: %s", username, load) self.connection_message = _('Backend connecting...') if not password: # Set backend token (no login request). # Do not include the token in the application logs ! logger.debug("Update backend token") self.backend.token = username self.connected = True self.connection_message = _('Backend connected') # Load data if load required... if load: self.load(reset=True) return self.connected try: # Backend real login logger.info("Requesting backend authentication, username: %s", username) self.connected = self.backend.login(username, password) if self.connected: self.connection_message = _('Connection successful') # Fetch the logged-in user users = self.find_object( 'contact', {'where': {'token': self.backend.token}} ) # Tag user as authenticated users[0].authenticated = True self.logged_in_user = users[0] # Get total objects count from the backend objects_count = self.get_objects_count(refresh=True, log=True) # Load data if load required... if load: self.load(reset=True) else: self.connection_message = _('Backend connection refused...') except BackendException as e: # pragma: no cover, should not happen logger.warning("configured applications backend is not available!") self.connection_message = e.message self.connected = False except Exception as e: # pragma: no cover, should not happen logger.warning("User login exception: %s", str(e)) logger.error("traceback: %s", traceback.format_exc()) logger.info("user_login, connection message: %s", self.connection_message) return self.connected def user_logout(self): """ Logout the data manager user. Do not log-out from the backend. Need to reset the datamanager to do it. """ self.logged_in_user = None def get_logged_user(self): """ Get the current logged in user """ return self.logged_in_user ## # Find objects and load objects cache ## def find_object(self, object_type, params=None, all_elements=False): """ Find an object ... Search in internal objects cache for an object matching the required parameters If params is a string, it is considered to be an object id and params is modified to {'_id': params}. Else, params is a dictionary of key/value to find a matching object in the objects cache If no objects are found in the cache, params is user to 'get' objects from the backend. Default behavior is to search in the backend if objects are not found in the cache. Call with backend=False to search only in local cache. If the backend search is successful, a new object is created if it exists a class in the imported modules (presumably alignak_webui.objects.item) which contains a 'bo_type' property and this property is valued as 'object_type'. Returns an array of matching objects """ logger.debug("find_object, self: %s, updated:%s", self, self.updated) logger.debug("find_object, %s, params: %s", object_type, params) # ----------------------------------------------------------------------------------------- # TODO: manage parameters like: # {'sort': '-opening_date', 'where': u'{"service_name": "userservice_1"}'} # -> ignore sort, ... but take car of 'where' to search in the cache! # ----------------------------------------------------------------------------------------- # if params is None: # params = {'page': 0, 'max_results': BACKEND_PAGINATION_LIMIT} unique_element = False if isinstance(params, basestring): params = {'where': {'_id': params}} unique_element = True logger.debug("find_object, %s, params: %s", object_type, params) items = [] # Update backend search parameters if params is None: params = {'page': 0, 'max_results': BACKEND_PAGINATION_LIMIT} if 'where' in params: params['where'] = json.dumps(params['where']) if 'embedded' in params: params['embedded'] = json.dumps(params['embedded']) if 'where' not in params: params['where'] = {} if 'page' not in params: params['page'] = 0 if 'max_results' not in params: params['max_results'] = BACKEND_PAGINATION_LIMIT logger.debug( "find_object, search in the backend for %s: parameters=%s", object_type, params ) try: if all_elements: result = self.backend.get_all(object_type, params=params) else: result = self.backend.get(object_type, params=params) except BackendException as e: # pragma: no cover, simple protection logger.warning("find_object, backend exception: %s", str(e)) return items logger.debug( "find_object, search result for %s: result=%s", object_type, result ) if result['_status'] != 'OK': # pragma: no cover, should not happen error = [] if "content" in result: error.append(result["content"]) if "_issues" in result: error.append(result["_issues"]) logger.warning("find_object, %s: %s, not found: %s", object_type, params, error) raise ValueError( '%s, where %s was not found in the backend, error: %s' % ( object_type, params, error ) ) if not result['_items']: # pragma: no cover, should occur rarely if items: logger.debug( "find_object, no data in backend, found in the cache: %s: %s", object_type, items ) return items logger.debug( "find_object, %s: %s: not found: %s", object_type, params, result ) raise ValueError( '%s, %s was not found in the cache nor in the backend' % ( object_type, params['where'] ) ) logger.debug("find_object, found in the backend: %s: %s", object_type, result['_items']) for item in result['_items']: # Find "Backend object type" classes in file imported modules ... for k, v in globals().items(): if isinstance(globals()[k], type) and '_type' in globals()[k].__dict__: if globals()[k].getType() == object_type: # Create a new object bo_object = globals()[k](item) items.append(bo_object) self.updated = datetime.utcnow() logger.debug("find_object, created: %s", bo_object) break else: # pragma: no cover, should not happen # No break, so not found a relative class! logger.error("find_object, %s class not found for: %s", object_type, item) return items def load(self, reset=False, refresh=False): """ Load data in the data manager objects If reset is set, then all the existing objects are deleted and then created from scratch (first load ...). Else, existing objects are updated and new objects are created. Get all the users (related to current logged-in user) Get all the user services (related to current logged-in user) Get all the relations between users and services Get the most recent sessions for each user service :returns: the number of newly created objects """ if not self.get_logged_user(): logger.error("load, must be logged-in before loading") return False if self.loading > 0: # pragma: no cover, protection if application shuts down ... logger.error("load, already loading: trial: %d", self.loading) if self.loading < 3: self.loading += 1 return False logger.error("load, already loading: reset counter") self.loading = 0 logger.debug("load, start loading: %s for %s", self, self.get_logged_user()) logger.debug( "load, start as administrator: %s", self.get_logged_user().is_administrator() ) start = time.time() if reset: logger.warning("Objects cache reset") self.reset(logout=False) self.loading += 1 self.loaded = False # Get internal objects count objects_count = self.get_objects_count() logger.debug("Load, start, objects in cache: %d", objects_count) # ----------------------------------------------------------------------------------------- # Get all users if current user is an administrator # ----------------------------------------------------------------------------------------- self.get_users() # ----------------------------------------------------------------------------------------- # Get all commands # ----------------------------------------------------------------------------------------- commands = self.get_commands() # ----------------------------------------------------------------------------------------- # Get all hosts # ----------------------------------------------------------------------------------------- hosts = self.get_hosts() # Get internal objects count new_objects_count = self.get_objects_count() logger.debug("Load, end, objects in cache: %d", new_objects_count) logger.warning( "Data manager load (%s), new objects: %d, duration: %s", refresh, new_objects_count - objects_count, (time.time() - start) ) if new_objects_count > objects_count: self.require_refresh() self.loaded = True self.loading = 0 return new_objects_count - objects_count def reset(self, logout=False): """ Reset data in the data manager objects """ logger.info("Data manager reset...") # Clean internal objects cache for known_class in self.known_classes: logger.info("Cleaning %s cache...", known_class.getType()) known_class.cleanCache() if logout: self.backend.logout() self.user_logout() self.loaded = False self.loading = 0 self.refresh_required = True def require_refresh(self): ''' Require an immediate refresh ''' self.refresh_required = True self.refresh_done = False def get_objects_count(self, object_type=None, refresh=False, log=False, search=None): ''' Get the count of the objects stored in the data manager cache If an object_type is specified, only returns the count for this object type If refresh is True, get the total count from the backend. This is only useful if total count is required... If log is set, an information log is made ''' log_function = logger.debug if log: log_function = logger.info if object_type: for known_class in self.known_classes: if object_type == known_class.getType(): objects_count = known_class.getCount() log_function( "get_objects_count, currently %d cached %ss", objects_count, object_type ) if refresh: if hasattr(known_class, '_total_count'): objects_count = known_class.getTotalCount() log_function( "get_objects_count, got _total_count attribute: %d", objects_count ) else: objects_count = self.count_objects(object_type, search=search) log_function( "get_objects_count, currently %d total %ss for %s", objects_count, object_type, search ) return objects_count else: # pragma: no cover, should not happen logger.warning("count_objects, unknown object type: %s", object_type) return 0 objects_count = 0 for known_class in self.known_classes: count = known_class.getCount() log_function( "get_objects_count, currently %d cached %ss", count, known_class.getType() ) if refresh: count = self.count_objects(known_class.getType(), search=search) log_function( "get_objects_count, currently %d total %ss for %s", count, known_class.getType(), search ) objects_count += count log_function("get_objects_count, currently %d elements", objects_count) return objects_count ## # # Elements add, delete, update, ... ## def count_objects(self, object_type, search=None): """ Request objects from the backend to pick-up total records count. Make a simple request for 1 element and we will get back the total count of elements search is a dictionary of key / value to search for If log is set, an information log is made """ total = 0 params = { 'page': 0, 'max_results': 1 } if search is not None: params['where'] = json.dumps(search) # Request objects from the backend ... try: resp = self.backend.get(object_type, params) logger.debug("count_objects %s: %s", object_type, resp) # Total number of records if '_meta' in resp: total = int(resp['_meta']['total']) except BackendException as e: logger.warning( "count_objects exception for object type: %s: %s", object_type, e.message ) return total def add_object(self, object_type, data=None, files=None): """ Add an element """ logger.info("add_object, request to add a %s: data: %s", object_type, data) # Do not set header to use the client default behavior: # - set headers as {'Content-Type': 'application/json'} # - encode provided data to JSON headers = None if files: logger.info("add_object, request to add a %s with files: %s", object_type, files) # Set header to disable client default behavior headers = {'Content-type': 'multipart/form-data'} try: result = self.backend.post(object_type, data=data, files=files, headers=headers) logger.debug("add_object, response: %s", result) if result['_status'] != 'OK': logger.warning("add_object, error: %s", result) return None self.find_object(object_type, result['_id']) except BackendException as e: logger.error("add_object, backend exception: %s", str(e)) return None except ValueError as e: # pragma: no cover, should never happen logger.warning("add_object, error: %s", str(e)) return None return result['_id'] def delete_object(self, object_type, element): """ Delete an element - object_type is the element type - element may be a string. In this case it is considered to be the element id """ logger.info("delete_object, request to delete the %s: %s", object_type, element) if isinstance(element, basestring): object_id = element else: object_id = element.get_id() try: # Get most recent version of the element items = self.find_object(object_type, object_id) element = items[0] except ValueError: # pragma: no cover, should never happen logger.warning("delete_object, object %s, _id=%s not found", object_type, object_id) return False try: # Request deletion headers = {'If-Match': element['_etag']} endpoint = '/'.join([object_type, object_id]) logger.info("delete_object, endpoint: %s", endpoint) result = self.backend.delete(endpoint, headers) logger.debug("delete_object, response: %s", result) if result['_status'] != 'OK': # pragma: no cover, should never happen error = [] if "content" in result: error.append(result["content"]) if "_issues" in result: error.append(result["_issues"]) for issue in result["_issues"]: error.append(result["_issues"][issue]) logger.warning("delete_object, error: %s", error) return False except BackendException as e: # pragma: no cover, should never happen logger.error("delete_object, backend exception: %s", str(e)) return False except ValueError as e: # pragma: no cover, should never happen logger.warning("delete_object, not found %s: %s", object_type, element) return False try: # Try to get most recent version of the element items = self.find_object(object_type, object_id) except ValueError: logger.info("delete_object, object deleted: %s, _id=%s", object_type, object_id) # Object deletion element._delete() return True def update_object(self, object_type, element, data): """ Update an element - object_type is the element type - element may be a string. In this case it is considered to be the element id """ logger.info("update_object, request to update the %s: %s", object_type, element) if isinstance(element, basestring): object_id = element else: object_id = element.get_id() try: # Get most recent version of the element items = self.find_object(object_type, object_id) element = items[0] except ValueError: logger.warning("update_object, object %s, _id=%s not found", object_type, object_id) return False try: # Request update headers = {'If-Match': element['_etag']} endpoint = '/'.join([object_type, object_id]) logger.info("update_object, endpoint: %s, data: %s", endpoint, data) result = self.backend.patch(endpoint, data, headers) logger.debug("update_object, response: %s", result) if result['_status'] != 'OK': # pragma: no cover, should never happen error = [] if "content" in result: error.append(result["content"]) if "_issues" in result: error.append(result["_issues"]) for issue in result["_issues"]: error.append(result["_issues"][issue]) logger.warning("update_object, error: %s", error) return False items = self.find_object(object_type, object_id) logger.info("update_object, updated: %s", items[0]) except BackendException as e: # pragma: no cover, should never happen logger.error("update_object, backend exception: %s", str(e)) return False except ValueError: # pragma: no cover, should never happen logger.warning("update_object, not found %s: %s", object_type, element) return False return True ## # Hosts ## def get_hosts(self, search=None): """ Get a list of all hosts. """ if search is None: search = {} if 'embedded' not in search: search.update({'embedded': {'userservice_session': 1, 'user': 1}}) try: logger.info("get_hosts, search: %s", search) items = self.find_object('host', search) logger.info("get_hosts, got: %d elements, %s", len(items), items) return items except ValueError: logger.debug("get_hosts, none found") return [] def get_host(self, search): """ Get a host by its id (default). """ if isinstance(search, basestring): search = {'max_results': 1, 'where': {'_id': search}} elif 'max_results' not in search: search.update({'max_results': 1}) items = self.get_hosts(search=search) return items[0] if items else None def get_hosts_synthesis(self, elts=None): """ Hosts synthesis by status """ if elts: hosts = [item for item in elts if item.getType() == 'host'] else: # Use internal object list ... hosts = [item for _id, item in Host.getCache().items()] logger.debug("get_hosts_synthesis, %d hosts", len(hosts)) synthesis = dict() synthesis['nb_elts'] = len(hosts) synthesis['nb_problem'] = 0 if hosts: for state in 'up', 'unreachable', 'down', 'unknown': synthesis['nb_' + state] = sum( 1 for host in hosts if host.get_status().lower() == state ) synthesis['pct_' + state] = round( 100.0 * synthesis['nb_' + state] / synthesis['nb_elts'], 2 ) else: for state in 'up', 'unreachable', 'down', 'unknown': synthesis['nb_' + state] = 0 synthesis['pct_' + state] = 0 logger.debug("get_hosts_synthesis: %s", synthesis) return synthesis def add_host(self, data, files): """ Add a host. Update the concerned session internal objects. """ return self.add_object('host', data, files) ## # Commands ## def get_commands(self, search=None): """ Get a list of all commands. """ if search is None: search = {} if 'embedded' not in search: search.update({'embedded': {'userservice_session': 1, 'user': 1}}) try: logger.info("get_commands, search: %s", search) items = self.find_object('command', search) return items except ValueError: logger.debug("get_commands, none found") return [] def get_command(self, search): """ Get a command by its id. """ if isinstance(search, basestring): search = {'max_results': 1, 'where': {'_id': search}} elif 'max_results' not in search: search.update({'max_results': 1}) items = self.get_commands(search=search) return items[0] if items else None def get_commands_synthesis(self, elts=None): """ Documents synthesis by status """ if elts: commands = [item for item in elts if item.getType() == 'command'] else: # Use internal object list ... commands = [item for _id, item in Command.getCache().items()] logger.debug("get_commands_synthesis, %d commands", len(commands)) synthesis = dict() synthesis['nb_elts'] = len(commands) if commands: for state in 'attached', 'empty', 'problem', 'unknown': synthesis['nb_' + state] = sum( 1 for command in commands if command.get_status().lower() == state ) synthesis['pct_' + state] = round( 100.0 * synthesis['nb_' + state] / synthesis['nb_elts'], 2 ) else: for state in 'attached', 'empty', 'problem', 'unknown': synthesis['nb_' + state] = 0 synthesis['pct_' + state] = 0 logger.debug("get_commands_synthesis: %s", synthesis) return synthesis def add_command(self, data): # pragma: no cover, not yet implemented! """ Add a command. Update the concerned session internal objects. """ return self.add_object('command', data) ## # Users ## def get_users(self, search=None): """ Get a list of known users """ if not self.get_logged_user().is_administrator(): return [self.get_logged_user()] try: logger.info("get_users, search: %s", search) items = self.find_object('contact', search) return items except ValueError: logger.debug("get_users, none found") return [] def get_user(self, search): """ Get a user by its id or a search pattern """ if isinstance(search, basestring): search = {'max_results': 1, 'where': {'_id': search}} elif 'max_results' not in search: search.update({'max_results': 1}) items = self.get_users(search=search) return items[0] if items else None def add_user(self, data): """ Add a user. """ return self.add_object('user', data) def delete_user(self, user): """ Delete a user. Cannot delete the currently logged in user ... If user is a string it is assumed to be the User object id to be searched in the objects cache. :param user: User object instance :type user: User (or string) Returns True/False depending if user closed """ logger.info("delete_user, request to delete the user: %s", user) if isinstance(user, basestring): user = self.get_user(user) if not user: return False user_id = user.get_id() if user_id == self.get_logged_user().get_id(): logger.warning("delete_user, request to delete the current logged-in user: %s", user_id) return False return self.delete_object('user', user)
class AlignakBackendBroker(BaseModule): """ This class is used to send logs and livestate to alignak-backend """ def __init__(self, mod_conf): """Module initialization mod_conf is a dictionary that contains: - all the variables declared in the module configuration file - a 'properties' value that is the module properties as defined globally in this file :param mod_conf: module configuration file as a dictionary """ BaseModule.__init__(self, mod_conf) # pylint: disable=global-statement global logger logger = logging.getLogger('alignak.module.%s' % self.alias) logger.setLevel(getattr(mod_conf, 'log_level', logging.INFO)) logger.debug("inner properties: %s", self.__dict__) logger.debug("received configuration: %s", mod_conf.__dict__) self.client_processes = int(getattr(mod_conf, 'client_processes', 1)) logger.info("Number of processes used by backend client: %s", self.client_processes) self.default_realm = None logger.info("StatsD configuration: %s:%s, prefix: %s, enabled: %s", getattr(mod_conf, 'statsd_host', 'localhost'), int(getattr(mod_conf, 'statsd_port', '8125')), getattr(mod_conf, 'statsd_prefix', 'alignak'), (getattr(mod_conf, 'statsd_enabled', '0') != '0')) self.statsmgr = Stats() self.statsmgr.register( self.alias, 'module', statsd_host=getattr(mod_conf, 'statsd_host', 'localhost'), statsd_port=int(getattr(mod_conf, 'statsd_port', '8125')), statsd_prefix=getattr(mod_conf, 'statsd_prefix', 'alignak'), statsd_enabled=(getattr(mod_conf, 'statsd_enabled', '0') != '0')) self.url = getattr(mod_conf, 'api_url', 'http://localhost:5000') logger.info("Alignak backend endpoint: %s", self.url) self.backend_connected = False self.backend_connection_retry_planned = 0 try: self.backend_connection_retry_delay = int( getattr(mod_conf, 'backend_connection_retry_delay', '10')) except ValueError: self.backend_connection_retry_delay = 10 logger.info("backend connection retry delay: %.2f seconds", self.backend_connection_retry_delay) self.backend_errors_count = 0 self.backend_username = getattr(mod_conf, 'username', '') self.backend_password = getattr(mod_conf, 'password', '') self.backend_generate = getattr(mod_conf, 'allowgeneratetoken', False) self.backend_count = int(getattr(mod_conf, 'backend_count', '50')) logger.info("backend pagination count: %d items", self.backend_count) self.backend_token = getattr(mod_conf, 'token', '') self.backend = Backend(self.url, self.client_processes) self.manage_update_program_status = getattr(mod_conf, 'update_program_status', '0') == '1' logger.info("manage update_program_status broks: %s", self.manage_update_program_status) # Log in to the backend self.logged_in = False self.backend_connected = self.backend_connection() # Get the default realm self.default_realm = self.get_default_realm() self.ref_live = {'host': {}, 'service': {}, 'user': {}} self.mapping = {'host': {}, 'service': {}, 'user': {}} # Objects reference self.load_protect_delay = int( getattr(mod_conf, 'load_protect_delay', '300')) self.last_load = 0 # Backend to be posted data self.logcheckresults = [] # Common functions def do_loop_turn(self): """This function is called/used when you need a module with a loop function (and use the parameter 'external': True) Note: We are obliged to define this method (even if not called!) because it is an abstract function in the base class """ logger.info("In loop") time.sleep(1) def raise_backend_alert(self, errors_count=10): """Raise a backend alert :return: True if the backend is not connected and the error count is greater than a defined threshold """ logger.debug( "Check backend connection, connected: %s, errors count: %d", self.backend_connected, self.backend_errors_count) if not self.backend_connected and self.backend_errors_count >= errors_count: return True return False def backend_connection(self): """Backend connection to check live state update is allowed :return: True/False """ if self.backend_login(): self.get_default_realm() try: start = time.time() params = {'where': '{"token":"%s"}' % self.backend.token} users = self.backend.get('user', params) self.statsmgr.counter('backend-get.user', 1) self.statsmgr.timer('backend-get-time.user', time.time() - start) except BackendException as exp: logger.warning( "Error on backend when retrieving user information: %s", exp) else: try: for item in users['_items']: self.logged_in = item['can_update_livestate'] return self.logged_in except Exception as exp: logger.error( "Can't get the user information in the backend response: %s", exp) logger.error("Configured user account is not allowed for this module") return False def backend_login(self): """Log in to the backend :return: bool """ generate = 'enabled' if not self.backend_generate: generate = 'disabled' if self.backend_token: # We have a token, don't ask for a new one self.backend.token = self.backend_token connected = True # Not really yet, but assume yes else: if not self.backend_username or not self.backend_password: logger.error( "No user or password supplied, and no default token defined. " "Can't connect to backend") connected = False else: try: start = time.time() connected = self.backend.login(self.backend_username, self.backend_password, generate) self.statsmgr.counter('backend-login', 1) self.statsmgr.timer('backend-login-time', time.time() - start) except BackendException as exp: logger.error("Error on backend login: %s", exp) connected = False return connected def get_default_realm(self): """ Retrieves the default top level realm for the connected user :return: str or None """ default_realm = None if self.backend_connected: try: start = time.time() result = self.backend.get('/realm', { 'max_results': 1, 'sort': '_level' }) self.statsmgr.counter('backend-get.realm', 1) self.statsmgr.timer('backend-get-time.realm', time.time() - start) except BackendException as exp: logger.warning( "Error on backend when retrieving default realm: %s", exp) else: try: default_realm = result['_items'][0]['_id'] except Exception as exp: logger.error( "Can't get the default realm in the backend response: %s", exp) return default_realm def get_refs(self): """ Get the _id in the backend for hosts, services and users :return: None """ start = time.time() now = int(time.time()) logger.info("Got a new configuration, reloading objects...") # Get managed inter-process dicts host_mapping = self.mapping['host'] serv_mapping = self.mapping['service'] user_mapping = self.mapping['user'] host_ref_live = self.ref_live['host'] serv_ref_live = self.ref_live['service'] user_ref_live = self.ref_live['user'] if now - self.last_load > self.load_protect_delay: logger.info("Got a new configuration, reloading objects...") # Updating hosts hosts = {} params = { 'projection': '{"name":1,"ls_state":1,"ls_state_type":1,"_realm":1}', 'max_results': self.backend_count, 'where': '{"_is_template":false}' } content = self.backend.get_all('host', params) self.statsmgr.counter('backend-getall.host', 1) for item in content['_items']: host_mapping[item['name']] = item['_id'] host_ref_live[item['_id']] = { '_id': item['_id'], '_etag': item['_etag'], '_realm': item['_realm'], 'initial_state': item['ls_state'], 'initial_state_type': item['ls_state_type'] } hosts[item['_id']] = item['name'] logger.info("- hosts references reloaded") # Updating services params = { 'projection': '{"host":1,"name":1,"ls_state":1,"ls_state_type":1,"_realm":1}', 'max_results': self.backend_count, 'where': '{"_is_template":false}' } content = self.backend.get_all('service', params) self.statsmgr.counter('backend-getall.service', 1) for item in content['_items']: try: serv_mapping['__'.join([hosts[item['host']], item['name']])] = item['_id'] serv_ref_live[item['_id']] = { '_id': item['_id'], '_etag': item['_etag'], '_realm': item['_realm'], 'initial_state': item['ls_state'], 'initial_state_type': item['ls_state_type'] } except KeyError: logger.warning("Got a service for an unknown host") logger.info("- services references reloaded") # Updating users params = { 'projection': '{"name":1,"_realm":1}', 'max_results': self.backend_count, 'where': '{"_is_template":false}' } content = self.backend.get_all('user', params) self.statsmgr.counter('backend-getall.user', 1) for item in content['_items']: user_mapping[item['name']] = item['_id'] user_ref_live[item['_id']] = { '_id': item['_id'], '_etag': item['_etag'], '_realm': item['_realm'] } logger.info("- users references reloaded") self.last_load = now else: logger.warning( "- references not reloaded. Last reload is too recent; " "set the 'load_protect_delay' parameter accordingly.") # Propagate changes in the inter-process dicts self.mapping['host'] = host_mapping self.mapping['service'] = serv_mapping self.mapping['user'] = user_mapping self.ref_live['host'] = host_ref_live self.ref_live['service'] = serv_ref_live self.ref_live['user'] = user_ref_live end = time.time() self.statsmgr.timer('backend-getall.time', end - start) return True def update_next_check(self, data, obj_type): """Update livestate host and service next check timestamp {'instance_id': u'475dc864674943b4aa4cbc966f7cc737', u'service_description': u'nsca_disk', u'next_chk': 0, u'in_checking': True, u'host_name': u'ek3022fdj-00011'} :param data: dictionary of data from scheduler :type data: dict :param obj_type: type of data (host | service) :type obj_type: str :return: False if backend update problem :rtype: bool """ logger.debug("Update next check: %s, %s", obj_type, data) if obj_type == 'host': if data['host_name'] in self.mapping['host']: # Received data for an host: data_to_update = {'ls_next_check': data['next_chk']} # Update live state return self.send_to_backend('livestate_host', data['host_name'], data_to_update) elif obj_type == 'service': service_name = '__'.join( [data['host_name'], data['service_description']]) if service_name in self.mapping['service']: # Received data for a service: data_to_update = {'ls_next_check': data['next_chk']} # Update live state return self.send_to_backend('livestate_service', service_name, data_to_update) return False def check_result(self, data): """ Got a check result for an host/service :param data: brok data got from scheduler :type data: dict :return: False if any error when posting to the backend """ logger.debug("Manage a check result: %s", data) # Received data for an host or service # Obliged to set an _realm... despite it is unuseful. posted_data = { '_realm': self.default_realm, 'state': data['state'], 'state_type': data['state_type'], 'state_id': data['state_id'], 'passive_check': data['passive_check'] if 'passive_check' in data else False, 'acknowledged': data['problem_has_been_acknowledged'], 'acknowledgement_type': data['acknowledgement_type'], 'downtimed': data['in_scheduled_downtime'], 'last_check': data['last_chk'], 'last_state': data['last_state'], 'last_state_id': data['last_state_id'], 'last_state_type': data['last_state_type'], 'output': data['output'], 'long_output': data['long_output'], 'perf_data': data['perf_data'], 'latency': data['latency'], 'execution_time': data['execution_time'], 'current_attempt': data['attempt'], # 'state_changed': data['state_changed'], 'last_state_changed': data['last_state_change'], 'last_hard_state_changed': data['last_hard_state_change'], } if 'service_description' in data: posted_data.update({ 'host_name': data['host_name'], 'service_name': data['service_description'], # Last time in the corresponding state 'last_time_0': data['last_time_ok'], 'last_time_1': data['last_time_warning'], 'last_time_2': data['last_time_critical'], 'last_time_3': data['last_time_unknown'], 'last_time_4': data['last_time_unreachable'] }) else: posted_data.update({ 'host_name': data['host_name'], # Last time in the corresponding state 'last_time_0': data['last_time_up'], 'last_time_1': data['last_time_down'], 'last_time_2': 0, 'last_time_3': 0, 'last_time_4': data['last_time_unreachable'] }) # Not managed currently # if 'initial_state' in self.ref_live['host'][h_id]: # data_to_update['ls_last_state'] = \ # self.ref_live['host'][h_id]['initial_state'] # data_to_update['ls_last_state_type'] = \ # self.ref_live['host'][h_id]['initial_state_type'] # del self.ref_live['host'][h_id]['initial_state'] # del self.ref_live['host'][h_id]['initial_state_type'] self.logcheckresults.append(posted_data) def update_status(self, brok): # pylint: disable=too-many-locals """We manage the status change for a backend host/service/contact :param brok: the brok :type brok: :return: None """ if 'contact_name' in brok.data: contact_name = brok.data['contact_name'] if brok.data['contact_name'] not in self.mapping['user']: logger.warning("Got a brok for an unknown user: '******'", contact_name) return None endpoint = 'user' name = contact_name item_id = self.mapping['user'][name] else: host_name = brok.data['host_name'] if brok.data['host_name'] not in self.mapping['host']: logger.warning("Got a brok for an unknown host: '%s'", host_name) return None endpoint = 'host' name = host_name item_id = self.mapping['host'][name] if 'service_description' in brok.data: service_name = '__'.join( [host_name, brok.data['service_description']]) endpoint = 'service' name = service_name item_id = self.mapping['service'][name] if service_name not in self.mapping['service']: logger.warning("Got a brok for an unknown service: '%s'", service_name) return None # Sort brok properties sorted_brok_properties = sorted(brok.data) logger.debug("Update status %s: %s", endpoint, sorted(brok.data)) # Search the concerned element start = time.time() self.statsmgr.counter('backend-get.%s' % endpoint, 1) item = self.backend.get(endpoint + '/' + item_id) self.statsmgr.timer('backend-get-time.%s' % endpoint, time.time() - start) logger.debug("Found %s: %s", endpoint, sorted(item)) differences = {} for key in sorted_brok_properties: value = brok.data[key] # Filter livestate keys... if "ls_%s" % key in item: logger.debug("Filtered live state: %s", key) continue # Filter noisy keys... if key in ["display_name", "tags", "notificationways"]: logger.debug("Filtered noisy key: %s", key) continue # Filter linked objects... if key in [ 'parents', 'parent_dependencies', 'check_command', 'event_handler', 'snapshot_command', 'check_period', 'maintenance_period', 'snapshot_period', 'notification_period', 'host_notification_period', 'service_notification_period', 'host_notification_commands', 'service_notification_commands', 'contacts', 'contact_groups', 'hostgroups', 'checkmodulations' ]: logger.debug("Filtered linked object: %s", key) continue if key not in item: logger.debug("Not existing: %s", key) continue if item[key] != value: if isinstance(value, bool): logger.debug("Different (%s): '%s' != '%s'!", key, item[key], value) differences.update({key: value}) elif not item[key] and not value: logger.info( "Different but empty fields (%s): '%s' != " "'%s' (brok), types: %s / %s", key, item[key], value, type(item[key]), type(value)) else: logger.debug("Different (%s): '%s' != '%s'!", key, item[key], value) differences.update({key: value}) else: logger.debug("Identical (%s): '%s'.", key, value) update = False if differences: logger.info("%s / %s, some modifications exist: %s.", endpoint, item['name'], differences) headers = { 'Content-Type': 'application/json', 'If-Match': item['_etag'] } try: start = time.time() self.statsmgr.counter('backend-patch.%s' % endpoint, 1) response = self.backend.patch( '%s/%s' % (endpoint, item['_id']), differences, headers, True) self.statsmgr.counter('backend-patch.%s' % endpoint, 1) self.statsmgr.timer('backend-patch-time.%s' % endpoint, time.time() - start) if response[ '_status'] == 'ERR': # pragma: no cover - should not happen logger.warning("Update %s: %s failed, errors: %s.", endpoint, name, response['_issues']) else: update = True logger.info("Updated %s: %s.", endpoint, name) if endpoint == 'host': self.ref_live['host'][self.mapping['host'][host_name]]['_etag'] = \ response['_etag'] elif endpoint == 'service': self.ref_live['service'][self.mapping['service'][service_name]]['_etag'] = \ response['_etag'] except BackendException as exp: # pragma: no cover - should not happen logger.error("Update %s '%s' failed", endpoint, name) logger.error("Data: %s", differences) if exp.code == 404: logger.error('Seems the %s %s deleted in the Backend', endpoint, name) elif exp.code == 412: logger.error('Seems the %s %s was modified in the Backend', endpoint, name) else: logger.exception("Exception: %s", exp) self.backend_connected = False self.backend_connection_retry_planned = \ int(time.time()) + self.backend_connection_retry_delay return update def update_program_status(self, brok): """Manage the whole program status change `program_status` brok is raised on program start whereas `update_program_status` brok is raised on every scheduler loop. `program_status` and `update_program_status` broks may contain: { # Some general information u'alignak_name': u'arbiter-master', u'instance_id': u'176064a1b30741d39452415097807ab0', u'instance_name': u'scheduler-master', # Some running information u'program_start': 1493969754, u'daemon_mode': 1, u'pid': 68989, u'last_alive': 1493970641, u'last_command_check': 1493970641, u'last_log_rotation': 1493970641, u'is_running': 1, # Some configuration parameters u'process_performance_data': True, u'passive_service_checks_enabled': True, u'event_handlers_enabled': True, u'command_file': u'', u'global_host_event_handler': None, u'interval_length': 60, u'modified_host_attributes': 0, u'check_external_commands': True, u'modified_service_attributes': 0, u'passive_host_checks_enabled': True, u'global_service_event_handler': None, u'notifications_enabled': True, u'check_service_freshness': True, u'check_host_freshness': True, u'flap_detection_enabled': True, u'active_service_checks_enabled': True, u'active_host_checks_enabled': True } :param brok: the brok :type brok: :return: None """ if 'alignak_name' not in brok.data: logger.warning( "Missing alignak_name in the brok data, " "the program status cannot be updated. " "Your Alignak framework version is too old to support this feature." ) return if not self.default_realm: logger.warning("Missing Alignak backend default realm, " "the program status cannot be updated. " "Your Alignak backend is in a very bad state!") return # Set event handlers as strings - simple protectection if 'global_host_event_handler' in brok.data and \ not isinstance(brok.data['global_host_event_handler'], str): brok.data['global_host_event_handler'] = str( brok.data['global_host_event_handler']) if 'global_service_event_handler' in brok.data and \ not isinstance(brok.data['global_service_event_handler'], str): brok.data['global_service_event_handler'] = \ str(brok.data['global_service_event_handler']) name = brok.data.pop('alignak_name') brok.data['name'] = name brok.data['_realm'] = self.default_realm params = {'sort': '_id', 'where': '{"name": "%s"}' % name} start = time.time() all_alignak = self.backend.get_all('alignak', params) self.statsmgr.counter('backend-getall.alignak', 1) self.statsmgr.timer('backend-getall-time.alignak', time.time() - start) logger.debug("Got %d Alignak configurations for %s", len(all_alignak['_items']), name) headers = {'Content-Type': 'application/json'} if not all_alignak['_items']: try: start = time.time() self.statsmgr.counter('backend-post.alignak', 1) response = self.backend.post('alignak', brok.data) self.statsmgr.timer('backend-post-time.alignak', time.time() - start) if response[ '_status'] == 'ERR': # pragma: no cover - should not happen logger.warning("Create alignak: %s failed, errors: %s.", name, response['_issues']) else: logger.info("Created alignak: %s.", name) except BackendException as exp: # pragma: no cover - should not happen logger.error("Create alignak '%s' failed", name) logger.error("Data: %s", brok.data) logger.exception("Exception: %s", exp) self.backend_connected = False self.backend_connection_retry_planned = \ int(time.time()) + self.backend_connection_retry_delay else: item = all_alignak['_items'][0] for key in item: if key not in brok.data: continue if item[key] == brok.data[key]: brok.data.pop(key) continue logger.debug("- updating: %s = %s", key, brok.data[key]) if not brok.data: logger.debug("Nothing to update") return headers['If-Match'] = item['_etag'] try: start = time.time() self.statsmgr.counter('backend-patch.alignak', 1) response = self.backend.patch('alignak/%s' % (item['_id']), brok.data, headers, True) self.statsmgr.timer('backend-patch-time.alignak', time.time() - start) if response[ '_status'] == 'ERR': # pragma: no cover - should not happen logger.warning("Update alignak: %s failed, errors: %s.", name, response['_issues']) else: logger.debug("Updated alignak: %s. %s", name, response) except BackendException as exp: # pragma: no cover - should not happen logger.error("Update alignak '%s' failed", name) logger.error("Data: %s", brok.data) if exp.code == 404: logger.error('Seems the alignak %s deleted in the Backend', name) elif exp.code == 412: logger.error( 'Seems the alignak %s was modified in the Backend', name) else: logger.exception("Exception: %s / %s", exp, exp.response) self.backend_connected = False self.backend_connection_retry_planned = \ int(time.time()) + self.backend_connection_retry_delay def update_actions(self, brok): """We manage the acknowledge and downtime broks :param brok: the brok :type brok: :return: None """ host_name = brok.data['host'] if host_name not in self.mapping['host']: logger.error( "Updating action for a brok for an unknown host: '%s'", host_name) return False service_name = '' if 'service' in brok.data: service_name = '__'.join([host_name, brok.data['service']]) if service_name not in self.mapping['service']: logger.error( "Updating action for a brok for an unknown service: '%s'", service_name) return False data_to_update = {} endpoint = 'actionacknowledge' if brok.type == 'acknowledge_raise': data_to_update['ls_acknowledged'] = True elif brok.type == 'acknowledge_expire': data_to_update['ls_acknowledged'] = False elif brok.type == 'downtime_raise': data_to_update['ls_downtimed'] = True endpoint = 'actiondowntime' elif brok.type == 'downtime_expire': data_to_update['ls_downtimed'] = False endpoint = 'actiondowntime' where = { 'processed': True, 'notified': False, 'host': self.mapping['host'][host_name], 'comment': brok.data['comment'], 'service': None } if 'service' in brok.data: # it's a service cr = self.send_to_backend('livestate_service', service_name, data_to_update) where['service'] = self.mapping['service'][service_name] else: # it's a host self.send_to_backend('livestate_host', host_name, data_to_update) params = {'where': json.dumps(where)} self.statsmgr.counter('backend-getall.%s' % endpoint, 1) actions = self.backend.get_all(endpoint, params) if actions['_items']: # case 1: the acknowledge / downtime come from backend, we update the 'notified' field # to True headers = { 'Content-Type': 'application/json', 'If-Match': actions['_items'][0]['_etag'] } self.statsmgr.counter('backend-patch.%s' % endpoint, 1) cr = self.backend.patch( endpoint + '/' + actions['_items'][0]['_id'], {"notified": True}, headers, True) return cr['_status'] == 'OK' # case 2: the acknowledge / downtime do not come from the backend, it's an external # command so we create a new entry where['notified'] = True # try find the user self.statsmgr.counter('backend-getall.user', 1) users = self.backend.get_all( 'user', {'where': '{"name":"' + brok.data['author'] + '"}'}) if users['_items']: where['user'] = users['_items'][0]['_id'] else: logger.error("User '%s' is unknown, ack/downtime is set by admin", brok.data['author']) users = self.backend.get_all('user', {'where': '{"name":"admin"}'}) where['user'] = users['_items'][0]['_id'] if brok.type in ['acknowledge_raise', 'downtime_raise']: where['action'] = 'add' else: where['action'] = 'delete' where['_realm'] = self.ref_live['host'][where['host']]['_realm'] if endpoint == 'actionacknowledge': if brok.data['sticky'] == 2: where['sticky'] = False else: where['sticky'] = True where['notify'] = bool(brok.data['notify']) elif endpoint == 'actiondowntime': where['start_time'] = int(brok.data['start_time']) where['end_time'] = int(brok.data['end_time']) where['fixed'] = bool(brok.data['fixed']) where['duration'] = int(brok.data['duration']) self.statsmgr.counter('backend-post.%s' % endpoint, 1) cr = self.backend.post(endpoint, where) return cr['_status'] == 'OK' def send_to_backend(self, type_data, name, data): """ Send data to alignak backend :param type_data: one of ['livestate_host', 'livestate_service', 'log_host', 'log_service'] :type type_data: str :param name: name of host or service :type name: str :param data: dictionary with data to add / update :type data: dict :return: True if send is ok, False otherwise :rtype: bool """ if not self.backend_connected and int( time.time() > self.backend_connection_retry_planned): self.backend_connected = self.backend_connection() if not self.backend_connected: logger.error("Alignak backend connection is not available. " "Skipping objects update.") return None logger.debug("Send to backend: %s, %s", type_data, data) headers = { 'Content-Type': 'application/json', } ret = True if type_data == 'livestate_host': headers['If-Match'] = self.ref_live['host'][self.mapping['host'] [name]]['_etag'] try: start = time.time() # self.statsmgr.counter('backend-patch.host', 1) response = self.backend.patch( 'host/%s' % self.ref_live['host'][self.mapping['host'][name]]['_id'], data, headers, True) self.statsmgr.timer('backend-patch-time.host', time.time() - start) if response[ '_status'] == 'ERR': # pragma: no cover - should not happen logger.error('%s', response['_issues']) ret = False else: self.ref_live['host'][self.mapping['host'] [name]]['_etag'] = response['_etag'] except BackendException as exp: # pragma: no cover - should not happen logger.error('Patch livestate for host %s error', self.mapping['host'][name]) logger.error('Data: %s', data) logger.exception("Exception: %s", exp) if exp.code == 404: logger.error('Seems the host %s deleted in the Backend', self.mapping['host'][name]) elif exp.code == 412: logger.error( 'Seems the host %s was modified in the Backend', self.mapping['host'][name]) ret = False else: self.backend_connected = False self.backend_connection_retry_planned = \ int(time.time()) + self.backend_connection_retry_delay elif type_data == 'livestate_service': service_id = self.mapping['service'][name] headers['If-Match'] = self.ref_live['service'][service_id]['_etag'] try: start = time.time() self.statsmgr.counter('backend-patch.service', 1) logger.debug("Send to backend: %s, %s (_etag: %s) - %s", type_data, name, self.ref_live['service'][service_id]['_etag'], data) response = self.backend.patch( 'service/%s' % self.ref_live['service'][service_id]['_id'], data, headers, True) self.statsmgr.timer('backend-patch-time.service', time.time() - start) if response[ '_status'] == 'ERR': # pragma: no cover - should not happen logger.error('%s', response['_issues']) ret = False else: self.ref_live['service'][service_id]['_etag'] = response[ '_etag'] logger.debug( "Updated _etag: %s, %s (_etag: %s)", type_data, name, self.ref_live['service'][self.mapping['service'] [name]]['_etag']) except BackendException as exp: # pragma: no cover - should not happen logger.error('Patch livestate for %s/%s %s error', type_data, name, self.mapping['service'][name]) logger.error('Data: %s', data) logger.exception("Exception: %s", exp) if exp.code == 404: logger.error('Seems the service %s deleted in the Backend', self.mapping['service'][name]) elif exp.code == 412: logger.error( 'Seems the service %s was modified in the Backend', self.mapping['service'][name]) ret = False else: self.backend_connected = False self.backend_connection_retry_planned = \ int(time.time()) + self.backend_connection_retry_delay elif type_data == 'lcrs': response = {'_status': 'OK'} try: logger.debug("Posting %d LCRs to the backend", len(self.logcheckresults)) while self.logcheckresults: start = time.time() lcrs = self.logcheckresults[:100] self.statsmgr.counter('backend-post.lcr', len(lcrs)) response = self.backend.post(endpoint='logcheckresult', data=lcrs) logger.debug("Posted %d LCRs", len(lcrs)) del self.logcheckresults[:100] self.logcheckresults = [] except BackendException as exp: # pragma: no cover - should not happen logger.error('Error when posting LCR to the backend, data: %s', self.logcheckresults) logger.error("Exception: %s", exp) self.backend_connected = False self.backend_connection_retry_planned = \ int(time.time()) + self.backend_connection_retry_delay else: self.statsmgr.timer('backend-post-time.lcr', time.time() - start) if response[ '_status'] == 'ERR': # pragma: no cover - should not happen logger.error( 'Error when posting LCR to the backend, data: %s', self.logcheckresults) logger.error('Issues: %s', response['_issues']) ret = False return ret def manage_brok(self, brok): """ We get the data to manage :param brok: Brok object :type brok: object :return: False if broks were not managed by the module """ if not self.logged_in: if not self.backend_connection(): logger.debug("Not logged-in, ignoring broks...") return False brok.prepare() logger.debug("manage_brok receives a Brok:") logger.debug("\t-Brok: %s - %s", brok.type, brok.data) try: endpoint = '' name = '' # Temporary: get concerned item for tracking received broks if 'contact_name' in brok.data: contact_name = brok.data['contact_name'] if brok.data['contact_name'] not in self.mapping['user']: logger.debug( "Got a brok %s for an unknown user: '******' (%s)", brok.type, contact_name, brok.data) return False endpoint = 'user' name = contact_name else: if 'host_name' in brok.data: host_name = brok.data['host_name'] if brok.data['host_name'] not in self.mapping['host']: logger.debug( "Got a brok %s for an unknown host: '%s' (%s)", brok.type, host_name, brok.data) return False endpoint = 'host' name = host_name if 'service_description' in brok.data: service_name = '__'.join( [host_name, brok.data['service_description']]) endpoint = 'service' name = service_name if service_name not in self.mapping['service']: logger.debug( "Got a brok %s for an unknown service: '%s' (%s)", brok.type, service_name, brok.data) return False if name: logger.debug("Received a brok: %s, for %s '%s'", brok.type, endpoint, name) else: logger.debug("Received a brok: %s", brok.type) logger.debug("Brok data: %s", brok.data) start = time.time() self.statsmgr.counter('managed-broks-type-count.%s' % brok.type, 1) ret = False if brok.type in ['new_conf']: ret = self.get_refs() if self.manage_update_program_status and \ brok.type in ['program_status', 'update_program_status']: self.update_program_status(brok) ret = None if brok.type == 'host_next_schedule': ret = self.update_next_check(brok.data, 'host') if brok.type == 'service_next_schedule': ret = self.update_next_check(brok.data, 'service') if brok.type in [ 'update_host_status', 'update_service_status', 'update_contact_status' ]: ret = self.update_status(brok) if brok.type in ['host_check_result', 'service_check_result']: self.check_result(brok.data) ret = None if brok.type in [ 'acknowledge_raise', 'acknowledge_expire', 'downtime_raise', 'downtime_expire' ]: ret = self.update_actions(brok) self.statsmgr.timer('managed-broks-type-time-%s' % brok.type, time.time() - start) return ret except Exception as exp: # pragma: no cover - should not happen logger.exception("Manage brok exception: %s", exp) return False def main(self): """ Main loop of the process This module is an "external" module :return: """ # Set the OS process title self.set_proctitle(self.alias) self.set_exit_handler() logger.info("starting...") while not self.interrupted: try: queue_size = self.to_q.qsize() if queue_size: logger.debug("queue length: %s", queue_size) self.statsmgr.gauge('queue-size', queue_size) # Reset backend lists self.logcheckresults = [] message = self.to_q.get_nowait() start = time.time() for brok in message: # Prepare and manage each brok in the queue message brok.prepare() self.manage_brok(brok) self.statsmgr.gauge('managed-broks-count', len(message)) logger.debug("time to manage %s broks (%d secs)", len(message), time.time() - start) self.statsmgr.timer('managed-broks-time', time.time() - start) if self.logcheckresults: self.send_to_backend('lcrs', None, None) except queue.Empty: # logger.debug("No message in the module queue") time.sleep(0.1) logger.info("stopping...") logger.info("stopped")
class AlignakBackendBroker(BaseModule): """ This class is used to send logs and livestate to alignak-backend """ def __init__(self, mod_conf): """ Module initialization mod_conf is a dictionary that contains: - all the variables declared in the module configuration file - a 'properties' value that is the module properties as defined globally in this file :param mod_conf: module configuration file as a dictionary """ BaseModule.__init__(self, mod_conf) # pylint: disable=global-statement global logger logger = logging.getLogger('alignak.module.%s' % self.alias) logger.debug("inner properties: %s", self.__dict__) logger.debug("received configuration: %s", mod_conf.__dict__) self.url = getattr(mod_conf, 'api_url', 'http://localhost:5000') self.backend = Backend(self.url) self.backend.token = getattr(mod_conf, 'token', '') self.backend_connected = False if self.backend.token == '': self.getToken(getattr(mod_conf, 'username', ''), getattr(mod_conf, 'password', ''), getattr(mod_conf, 'allowgeneratetoken', False)) self.logged_in = self.backendConnection() self.ref_live = { 'host': {}, 'service': {} } self.mapping = { 'host': {}, 'service': {} } self.hosts = {} self.services = {} self.loaded_hosts = False self.loaded_services = False # Common functions def do_loop_turn(self): """This function is called/used when you need a module with a loop function (and use the parameter 'external': True) """ logger.info("In loop") time.sleep(1) def getToken(self, username, password, generatetoken): """ Authenticate and get the token :param username: login name :type username: str :param password: password :type password: str :param generatetoken: if True allow generate token, otherwise not generate :type generatetoken: bool :return: None """ generate = 'enabled' if not generatetoken: generate = 'disabled' try: self.backend.login(username, password, generate) self.backend_connected = True except BackendException as exp: logger.warning("Alignak backend is not available for login. " "No backend connection.") logger.exception("Exception: %s", exp) self.backend_connected = False def backendConnection(self): """ Backend connection to check live state update is allowed :return: True/False """ params = {'where': '{"token":"%s"}' % self.backend.token} users = self.backend.get('user', params) for item in users['_items']: return item['can_update_livestate'] logger.error("Configured user account is not allowed for this module") return False def get_refs(self, type_data): """ Get the _id in the backend for hosts and services :param type_data: livestate type to get: livestate_host or livestate_service :type type_data: str :return: None """ if type_data == 'livestate_host': params = { 'projection': '{"name":1,"ls_state":1,"ls_state_type":1,"_realm":1}', 'where': '{"_is_template":false}' } content = self.backend.get_all('host', params) for item in content['_items']: self.mapping['host'][item['name']] = item['_id'] self.ref_live['host'][item['_id']] = { '_id': item['_id'], '_etag': item['_etag'], '_realm': item['_realm'], 'initial_state': item['ls_state'], 'initial_state_type': item['ls_state_type'] } self.loaded_hosts = True elif type_data == 'livestate_service': params = { 'projection': '{"name":1}', 'where': '{"_is_template":false}' } contenth = self.backend.get_all('host', params) hosts = {} for item in contenth['_items']: hosts[item['_id']] = item['name'] params = { 'projection': '{"host":1,"name":1,"ls_state":1,"ls_state_type":1,"_realm":1}', 'where': '{"_is_template":false}' } content = self.backend.get_all('service', params) for item in content['_items']: self.mapping['service'][''.join([hosts[item['host']], item['name']])] = item['_id'] self.ref_live['service'][item['_id']] = { '_id': item['_id'], '_etag': item['_etag'], '_realm': item['_realm'], 'initial_state': item['ls_state'], 'initial_state_type': item['ls_state_type'] } self.loaded_services = True def update(self, data, obj_type): """ Update livestate_host and livestate_service :param data: dictionary of data from scheduler :type data: dict :param obj_type: type of data (host | service) :type obj_type: str :return: Counters of updated or add data to alignak backend :rtype: dict """ start_time = time.time() counters = { 'livestate_host': 0, 'livestate_service': 0, 'log_host': 0, 'log_service': 0 } logger.debug("Got data to update: %s - %s", obj_type, data) if obj_type == 'host': if data['host_name'] in self.mapping['host']: # Received data for an host: # { # u'last_time_unreachable': 0, u'last_problem_id': 0, u'check_type': 1, # u'retry_interval': 0,u'last_event_id': 0, u'problem_has_been_acknowledged': False, # u'command_name': u'nsca_host_dead', u'last_state': u'UP', u'latency': 0, # u'last_state_type': u'HARD', u'last_hard_state_change': 0.0, # u'last_time_up': 1473597379, u'percent_state_change': 0.0, u'state': u'UP', # u'last_chk': 1473597379, # u'last_state_id': 0, u'end_time': 0, u'timeout': 0, u'current_event_id': 0, # u'execution_time': 0.0, u'start_time': 0, u'return_code': 0, # u'state_type': u'HARD', u'state_id': 0, u'in_checking': False, # u'early_timeout': 0, # u'in_scheduled_downtime': False, u'attempt': 1, u'state_type_id': 1, # u'acknowledgement_type': 1, u'last_state_change': 0.0, u'last_time_down': 0, # 'instance_id': u'd2d402f5de244d95b10d1b47d9891710', u'long_output': u'', # u'current_problem_id': 0, u'host_name': u'fvc320', u'check_interval': 0, # u'output': u'No message', u'has_been_checked': 1, u'perf_data': u'' # } data_to_update = { 'ls_state': data['state'], 'ls_state_id': data['state_id'], 'ls_state_type': data['state_type'], 'ls_last_check': data['last_chk'], 'ls_last_state': data['last_state'], 'ls_last_state_type': data['last_state_type'], 'ls_output': data['output'], 'ls_long_output': data['long_output'], 'ls_perf_data': data['perf_data'], 'ls_acknowledged': data['problem_has_been_acknowledged'], 'ls_downtimed': data['in_scheduled_downtime'], 'ls_latency': data['latency'] } h_id = self.mapping['host'][data['host_name']] if 'initial_state' in self.ref_live['host'][h_id]: data_to_update['ls_last_state'] = self.ref_live['host'][h_id]['initial_state'] data_to_update['ls_last_state_type'] = \ self.ref_live['host'][h_id]['initial_state_type'] del self.ref_live['host'][h_id]['initial_state'] del self.ref_live['host'][h_id]['initial_state_type'] data_to_update['_realm'] = self.ref_live['host'][h_id]['_realm'] logger.debug("host live state data: %s", data_to_update) # Update live state ret = self.send_to_backend('livestate_host', data['host_name'], data_to_update) if ret: counters['livestate_host'] += 1 # Add an host log data_to_update['ls_state_changed'] = ( data_to_update['ls_state'] != data_to_update['ls_last_state'] ) data_to_update['host'] = self.mapping['host'][data['host_name']] data_to_update['service'] = None # Rename ls_ keys... del data_to_update['ls_downtimed'] for key in data_to_update: if key.startswith('ls_'): data_to_update[key[3:]] = data_to_update[key] del data_to_update[key] ret = self.send_to_backend('log_host', data['host_name'], data_to_update) if ret: counters['log_host'] += 1 elif obj_type == 'service': service_name = ''.join([data['host_name'], data['service_description']]) if service_name in self.mapping['service']: # Received data for a service: # { # u'last_problem_id': 0, u'check_type': 0, u'retry_interval': 2, # u'last_event_id': 0, u'problem_has_been_acknowledged': False, # u'last_time_critical': 1473597376, # u'last_time_warning': 0, u'command_name': u'check_nrpe', u'last_state': u'OK', # u'latency': 2.4609699249, u'current_event_id': 1, u'last_state_type': u'HARD', # u'last_hard_state_change': 0.0, u'percent_state_change': 4.1, # u'state': u'CRITICAL', # u'last_chk': 1473597375, u'last_state_id': 0, u'host_name': u'denice', # u'check_interval': 5, u'last_time_unknown': 0, u'execution_time': 0.1133639812, # u'start_time': 0, u'return_code': 2, u'state_type': u'SOFT', u'state_id': 2, # u'service_description': u'Disk hda1', u'in_checking': False, u'early_timeout': 0, # u'in_scheduled_downtime': False, u'attempt': 1, u'state_type_id': 0, # u'acknowledgement_type': 1, u'last_state_change': 1473597376.147903, # 'instance_id': u'3ac88dd0c1c04b37a5d181622e93b5bc', u'long_output': u'', # u'current_problem_id': 1, u'last_time_ok': 0, u'timeout': 0, # u'output': u"NRPE: Command 'check_hda1' not defined", u'has_been_checked': 1, # u'perf_data': u'', u'end_time': 0 # } data_to_update = { 'ls_state': data['state'], 'ls_state_id': data['state_id'], 'ls_state_type': data['state_type'], 'ls_last_check': data['last_chk'], 'ls_last_state': data['last_state'], 'ls_last_state_type': data['last_state_type'], 'ls_output': data['output'], 'ls_long_output': data['long_output'], 'ls_perf_data': data['perf_data'], 'ls_acknowledged': data['problem_has_been_acknowledged'], 'ls_downtimed': data['in_scheduled_downtime'], 'ls_execution_time': data['execution_time'], 'ls_latency': data['latency'] } s_id = self.mapping['service'][service_name] if 'initial_state' in self.ref_live['service'][s_id]: data_to_update['ls_last_state'] = \ self.ref_live['service'][s_id]['initial_state'] data_to_update['ls_last_state_type'] = \ self.ref_live['service'][s_id]['initial_state_type'] del self.ref_live['service'][s_id]['initial_state'] del self.ref_live['service'][s_id]['initial_state_type'] data_to_update['_realm'] = self.ref_live['service'][s_id]['_realm'] logger.debug("service live state data: %s", data_to_update) # Update live state ret = self.send_to_backend('livestate_service', service_name, data_to_update) if ret: counters['livestate_service'] += 1 # Add a service log data_to_update['ls_state_changed'] = ( data_to_update['ls_state'] != data_to_update['ls_last_state'] ) data_to_update['host'] = self.mapping['host'][data['host_name']] data_to_update['service'] = self.mapping['service'][service_name] # Rename ls_ keys... del data_to_update['ls_downtimed'] for key in data_to_update: if key.startswith('ls_'): data_to_update[key[3:]] = data_to_update[key] del data_to_update[key] self.send_to_backend('log_service', service_name, data_to_update) if ret: counters['log_service'] += 1 if (counters['livestate_host'] + counters['livestate_service']) > 0: logger.debug("--- %s seconds ---", (time.time() - start_time)) return counters def send_to_backend(self, type_data, name, data): """ Send data to alignak backend :param type_data: one of ['livestate_host', 'livestate_service', 'log_host', 'log_service'] :type type_data: str :param name: name of host or service :type name: str :param data: dictionary with data to add / update :type data: dict :return: True if send is ok, False otherwise :rtype: bool """ if not self.backend_connected: logger.error("Alignak backend connection is not available. " "Skipping objects update.") return headers = { 'Content-Type': 'application/json', } ret = True if type_data == 'livestate_host': headers['If-Match'] = self.ref_live['host'][self.mapping['host'][name]]['_etag'] try: response = self.backend.patch( 'host/%s' % self.ref_live['host'][self.mapping['host'][name]]['_id'], data, headers, True) if response['_status'] == 'ERR': logger.error('%s', response['_issues']) ret = False else: self.ref_live['host'][self.mapping['host'][name]]['_etag'] = response['_etag'] except BackendException as exp: logger.error('Patch livestate for host %s error', self.mapping['host'][name]) logger.error('Data: %s', data) logger.exception("Exception: %s", exp) elif type_data == 'livestate_service': headers['If-Match'] = self.ref_live['service'][self.mapping['service'][name]]['_etag'] try: response = self.backend.patch( 'service/%s' % self.ref_live['service'][self.mapping['service'][name]]['_id'], data, headers, True) if response['_status'] == 'ERR': logger.error('%s', response['_issues']) ret = False else: self.ref_live['service'][self.mapping['service'][name]]['_etag'] = response[ '_etag'] except BackendException as exp: logger.error('Patch livestate for service %s error', self.mapping['service'][name]) logger.error('Data: %s', data) logger.exception("Exception: %s", exp) elif type_data == 'log_host': try: response = self.backend.post('logcheckresult', data) except BackendException as exp: logger.error('Post logcheckresult for host %s error', self.mapping['host'][name]) logger.error('Data: %s', data) logger.exception("Exception: %s", exp) ret = False elif type_data == 'log_service': try: response = self.backend.post('logcheckresult', data) except BackendException as exp: logger.error('Post logcheckresult for service %s error', self.mapping['service'][name]) logger.error('Data: %s', data) logger.exception("Exception: %s", exp) ret = False return ret def manage_brok(self, queue): """ We get the data to manage :param queue: Brok object :type queue: object :return: None """ if not self.logged_in: logger.debug("Not logged-in, ignoring broks...") return if not self.loaded_hosts: self.get_refs('livestate_host') if not self.loaded_services: self.get_refs('livestate_service') if queue.type == 'host_check_result': self.update(queue.data, 'host') elif queue.type == 'service_check_result': self.update(queue.data, 'service') def main(self): """ Main loop of the process This module is an "external" module :return: """ # Set the OS process title self.set_proctitle(self.alias) self.set_exit_handler() logger.info("starting...") while not self.interrupted: logger.debug("queue length: %s", self.to_q.qsize()) start = time.time() l = self.to_q.get() for b in l: b.prepare() self.manage_brok(b) logger.debug("time to manage %s broks (%d secs)", len(l), time.time() - start) logger.info("stopping...") logger.info("stopped")
class BackendConnection(object): # pylint: disable=too-few-public-methods """Base class for Alignak backend connection""" def __init__(self, backend_endpoint='http://127.0.0.1:5000'): self.backend_endpoint = backend_endpoint self.alignak_backend = Backend(backend_endpoint) self.connected = False def login(self, username, password=None): """Log into the backend If password is provided, use the backend login function to authenticate the user If no password is provided, the username is assumed to be an authentication token and we use the backend connect function.""" logger.info("login, connection requested, login: %s", username) self.connected = False if not username: # Refuse backend login without username logger.warning("No login without username!") return self.connected if not password: # Set backend token (no login request). logger.debug("Update backend token") self.alignak_backend.token = username self.connected = True return self.connected try: # Backend real login logger.info("Requesting backend (%s) authentication, username: %s", self.backend_endpoint, username) self.connected = self.alignak_backend.login(username, password) except BackendException: # pragma: no cover, should not happen logger.warning("configured backend is not available!") except Exception as e: # pragma: no cover, should not happen logger.warning("User login exception: %s", str(e)) logger.error("traceback: %s", traceback.format_exc()) logger.info("login result: %s", self.connected) return self.connected def logout(self): """Log out from the backend Do nothing except setting 'connected' attribute to False""" logger.info("logout") self.connected = False def count(self, object_type, params=None): """Count backend elements If params is a string, it is considered to be an object id and params is modified to {'_id': params}. Else, params is used to 'get' objects from the backend.""" logger.debug("count, %s, params: %s", object_type, params) if isinstance(params, string_types): params = {'where': {'_id': params}} # Update backend search parameters if params is None: params = {'page': 0, 'max_results': BACKEND_PAGINATION_LIMIT} if 'where' in params: params['where'] = json.dumps(params['where']) if 'embedded' in params: del params['embedded'] if 'where' not in params: params['where'] = {} if 'page' not in params: params['page'] = 0 if 'max_results' not in params: params['max_results'] = BACKEND_PAGINATION_LIMIT # if params is None: # params = {'page': 0, 'max_results': 1} # if 'where' in params: # params['where'] = json.dumps(params['where']) # if 'max_results' not in params: # params['max_results'] = 1 logger.debug("count, search in the backend for %s: parameters=%s", object_type, params) try: result = self.alignak_backend.get(object_type, params=params) except BackendException as e: # pragma: no cover, simple protection logger.warning("count, backend exception for %s: %s", object_type, str(e)) return 0 if not result['_status'] == 'OK': # pragma: no cover, should not happen error = [] if "content" in result: error.append(result['content']) if "_issues" in result: error.append(result['_issues']) logger.warning("count, %s: %s, not found: %s", object_type, params, error) return 0 logger.debug("count, search result for %s: status=%s", object_type, result['_status']) # If more than one element is found, we get an _items list if '_items' in result: logger.debug("count, found in the backend: %d %s", len(result['_items']), object_type) logger.debug("count, found in the backend: %d total %s", result['_meta']['total'], object_type) return result['_meta']['total'] return 0 # pragma: no cover, simple protection def get(self, object_type, params=None, all_elements=False): """Get backend elements If params is a string, it is considered to be an object id and params is modified to {'_id': params}. Else, params is used to 'get' objects from the backend. Returns an object or an array of matching objects. All extra attributes (_links, _status, _meta, ...) are not returned but an '_total' attribute is added in each element to get the total count of elements stored in the backend. Returns None if the search failed. Do not raise any exception to the caller. If all_elements is True, it calls the get_all function of the backend client to get all the elements without any pagination activated.""" logger.debug("get, %s, params: %s", object_type, params) if '/' not in object_type: # Do not Get a specific element, manage search parameters if isinstance(params, string_types): params = {'where': {'_id': params}} logger.debug("get, %s, params: %s", object_type, params) # Update backend search parameters if params is None: params = {'page': 0, 'max_results': BACKEND_PAGINATION_LIMIT} if 'where' in params: params['where'] = json.dumps(params['where']) if 'embedded' in params: params['embedded'] = json.dumps(params['embedded']) if 'where' not in params: params['where'] = {} if 'page' not in params: params['page'] = 0 if 'max_results' not in params: params['max_results'] = BACKEND_PAGINATION_LIMIT logger.debug("get, search in the backend for %s: parameters=%s, all: %s", object_type, params, all_elements) try: if all_elements: result = self.alignak_backend.get_all(object_type, params=params) else: result = self.alignak_backend.get(object_type, params=params) except BackendException as e: # pragma: no cover, simple protection logger.warning("get, backend exception for %s: %s", object_type, str(e)) raise BackendException(code=e.code, message=e.message) logger.debug("search, search result for %s: result=%s", object_type, result) if result['_status'] != 'OK': # pragma: no cover, should not happen error = [] if "content" in result: error.append(result['content']) if "_issues" in result: error.append(result['_issues']) logger.warning("get, %s: %s, not found: %s", object_type, params, error) raise ValueError('%s, search: %s was not found in the backend, error: %s' % (object_type, params, error)) # If more than one element is found, we get an _items list if '_items' in result: total = len(result['_items']) if '_meta' in result: total = result['_meta']['total'] logger.debug("get %s %s, %d total elements found in the backend", object_type, ' (All required)' if all_elements else ' (filtered)', total) for item in result['_items']: item.update({'_total': total}) return result['_items'] if '_status' in result: result.pop('_status') if '_meta' in result: result['_total'] = result['_meta']['total'] result.pop('_meta') if all_elements: logger.info("get %s %s, %d total elements found in the backend", object_type, ' (All required)' if all_elements else ' (filtered)', len(result)) for item in result: item.update({'_total': len(result)}) logger.debug("get, found one in the backend: %s: %s", object_type, result) return result def post(self, object_type, data=None, files=None): """Add an element""" logger.info("post, request to add a %s: data: %s", object_type, data) # Do not set header to use the client default behavior: # - set headers as {'Content-Type': 'application/json'} # - encode provided data to JSON headers = None if files: logger.info("post, request to add a %s with files: %s", object_type, files) # Set header to disable client default behavior headers = {'Content-type': 'multipart/form-data'} try: result = self.alignak_backend.post(object_type, data=data, files=files, headers=headers) logger.debug("post, response: %s", result) if result['_status'] != 'OK': logger.warning("post, error: %s", result) return None except BackendException as exp: # pragma: no cover, simple protection logger.exception("post, backend exception: %s", exp) error = [] if getattr(exp, "response", None) and "_issues" in exp.response: error = exp.response["_issues"] else: error.append(str(exp)) logger.warning("post, error(s): %s", error) return error except Exception as e: # pragma: no cover, simple protection logger.warning("post, error: %s", str(e)) return None return result['_id'] def delete(self, object_type, object_id): """Delete an element - object_type is the element type - object_id is the element identifier""" logger.info("delete, request to delete the %s: %s", object_type, object_id) try: # Get most recent version of the element element = self.get('/'.join([object_type, object_id])) logger.debug("delete, element: %s", element) except ValueError: # pragma: no cover, simple protection logger.warning("delete, object %s, _id=%s not found", object_type, object_id) return False try: # Request deletion headers = {'If-Match': element['_etag']} endpoint = '/'.join([object_type, object_id]) logger.debug("delete, endpoint: %s", endpoint) result = self.alignak_backend.delete(endpoint, headers) logger.debug("delete, response: %s", result) if result['_status'] != 'OK': # pragma: no cover, should never happen error = [] if "content" in result: error.append(result["content"]) if "_issues" in result: error.append(result["_issues"]) for issue in result["_issues"]: error.append(result["_issues"][issue]) logger.warning("delete, error: %s", error) return False except BackendException as e: # pragma: no cover, should never happen logger.error("delete, backend exception: %s", str(e)) return False except ValueError: # pragma: no cover, should never happen logger.warning("delete, not found %s: %s", object_type, element) return False return True def update(self, element, data): """Update an element""" logger.info("update, request to update: %s", element) try: # Request update headers = {'If-Match': element['_etag']} endpoint = '/'.join([element.get_type(), element.id]) logger.debug("update, endpoint: %s, data: %s", endpoint, data) result = self.alignak_backend.patch(endpoint, data, headers, inception=True) logger.debug("update, response: %s", result) if result['_status'] != 'OK': # pragma: no cover, should never happen error = [] if "content" in result: error.append(result["content"]) if "_issues" in result: error.append(result["_issues"]) for issue in result["_issues"]: error.append(result["_issues"][issue]) logger.warning("update, error(s): %s", error) return error except BackendException as e: # pragma: no cover, should never happen error = [] if e.response and "_issues" in e.response: error = e.response["_issues"] else: error.append(str(e)) logger.error("update, backend exception: %s", str(e)) return error except ValueError: # pragma: no cover, should never happen logger.warning("update, not found %s: %s", element.get_type(), element) return False return True
class AlignakBackendScheduler(BaseModule): """ This class is used to send live states to alignak-backend """ def __init__(self, mod_conf): """Module initialization mod_conf is a dictionary that contains: - all the variables declared in the module configuration file - a 'properties' value that is the module properties as defined globally in this file :param mod_conf: module configuration file as a dictionary """ BaseModule.__init__(self, mod_conf) # pylint: disable=global-statement global logger logger = logging.getLogger('alignak.module.%s' % self.alias) logger.setLevel(getattr(mod_conf, 'log_level', logging.INFO)) logger.debug("inner properties: %s", self.__dict__) logger.debug("received configuration: %s", mod_conf.__dict__) self.client_processes = int(getattr(mod_conf, 'client_processes', 1)) logger.info("Number of processes used by backend client: %s", self.client_processes) self.backend_count = int(getattr(mod_conf, 'backend_count', '50')) logger.info("backend pagination count: %d items", self.backend_count) logger.info("StatsD configuration: %s:%s, prefix: %s, enabled: %s", getattr(mod_conf, 'statsd_host', 'localhost'), int(getattr(mod_conf, 'statsd_port', '8125')), getattr(mod_conf, 'statsd_prefix', 'alignak'), (getattr(mod_conf, 'statsd_enabled', '0') != '0')) self.statsmgr = Stats() self.statsmgr.register( self.alias, 'module', statsd_host=getattr(mod_conf, 'statsd_host', 'localhost'), statsd_port=int(getattr(mod_conf, 'statsd_port', '8125')), statsd_prefix=getattr(mod_conf, 'statsd_prefix', 'alignak'), statsd_enabled=(getattr(mod_conf, 'statsd_enabled', '0') != '0')) self.url = getattr(mod_conf, 'api_url', 'http://localhost:5000') logger.info("Alignak backend endpoint: %s", self.url) self.backend = Backend(self.url, self.client_processes) self.backend.token = getattr(mod_conf, 'token', '') self.backend_connected = False self.backend_errors_count = 0 self.backend_username = getattr(mod_conf, 'username', '') self.backend_password = getattr(mod_conf, 'password', '') self.backend_generate = getattr(mod_conf, 'allowgeneratetoken', False) if not self.backend.token: logger.warning( "no user token configured. " "It is recommended to set a user token rather than a user login " "in the configuration. Trying to get a token from the provided " "user login information...") self.getToken() else: self.backend_connected = True # Common functions def do_loop_turn(self): """This function is called/used when you need a module with a loop function (and use the parameter 'external': True) """ logger.info("[Backend Scheduler] In loop") time.sleep(1) def getToken(self): """Authenticate and get the token :return: None """ generate = 'enabled' if not self.backend_generate: generate = 'disabled' try: start = time.time() self.backend_connected = self.backend.login( self.backend_username, self.backend_password, generate) self.statsmgr.counter('backend-login', 1) self.statsmgr.timer('backend-login-time', time.time() - start) if not self.backend_connected: logger.warning("Backend login failed") self.token = self.backend.token self.backend_errors_count = 0 except BackendException as exp: # pragma: no cover - should not happen self.backend_connected = False self.backend_errors_count += 1 logger.warning( "Alignak backend is not available for login. " "No backend connection, attempt: %d", self.backend_errors_count) logger.debug("Exception: %s", exp) def raise_backend_alert(self, errors_count=10): """Raise a backend alert :return: True if the backend is not connected and the error count is greater than a defined threshold """ logger.debug( "Check backend connection, connected: %s, errors count: %d", self.backend_connected, self.backend_errors_count) if not self.backend_connected and self.backend_errors_count >= errors_count: return True return False def hook_load_retention(self, scheduler): """Load retention data from alignak-backend :param scheduler: scheduler instance of alignak :type scheduler: object :return: None """ all_data = {'hosts': {}, 'services': {}} if not self.backend_connected: self.getToken() if self.raise_backend_alert(errors_count=1): logger.warning("Alignak backend connection is not available. " "Loading retention data is not possible.") return None if not self.backend_connected: return None # Get data from the backend try: start = time.time() params = {"max_results": self.backend_count} response = self.backend.get_all('alignakretention', params) for host in response['_items']: # clean unusable keys hostname = host['host'] if 'retention_services' in host: for service in host['retention_services']: all_data['services'][(host['host'], service)] = \ host['retention_services'][service] for key in [ '_created', '_etag', '_id', '_links', '_updated', 'host', 'retention_services', '_user', 'schema_version' ]: if key in host: del host[key] all_data['hosts'][hostname] = host logger.info('%d hosts loaded from retention', len(all_data['hosts'])) self.statsmgr.counter('retention-load.hosts', len(all_data['hosts'])) logger.info('%d services loaded from retention', len(all_data['services'])) self.statsmgr.counter('retention-load.services', len(all_data['services'])) self.statsmgr.timer('retention-load.time', time.time() - start) scheduler.restore_retention_data(all_data) except BackendException: self.backend_connected = False self.backend_errors_count += 1 logger.warning( "Alignak backend connection fails. Check and fix your configuration" ) return False return True def hook_save_retention(self, scheduler): """Save retention data to alignak-backend :param scheduler: scheduler instance of alignak :type scheduler: object :return: None """ if not self.backend_connected: self.getToken() if self.raise_backend_alert(errors_count=1): logger.warning("Alignak backend connection is not available. " "Saving objects is not possible.") return None if not self.backend_connected: return None try: data_to_save = scheduler.get_retention_data() start_time = time.time() # get list of retention_data params = {"max_results": self.backend_count} response = self.backend.get_all('alignakretention', params) db_hosts = {} for host in response['_items']: db_hosts[host['host']] = host # add services in the hosts for host in data_to_save['hosts']: data_to_save['hosts'][host]['retention_services'] = {} data_to_save['hosts'][host]['host'] = host if 'services' in data_to_save: # Scheduler old-school: two separate dictionaries! for service in data_to_save['services']: data_to_save['hosts'][service[0]]['retention_services'][service[1]] = \ data_to_save['services'][service] for host in data_to_save['hosts']: if host in db_hosts: # if host in retention_data, PUT headers = {'Content-Type': 'application/json'} headers['If-Match'] = db_hosts[host]['_etag'] try: logger.debug('Host retention data: %s', data_to_save['hosts'][host]) self.backend.put( 'alignakretention/%s' % (db_hosts[host]['_id']), data_to_save['hosts'][host], headers, True) except BackendException as exp: # pragma: no cover - should not happen logger.error('Put alignakretention error') logger.error('Response: %s', exp.response) logger.exception("Exception: %s", exp) self.backend_connected = False return False else: # if not host in retention_data, POST try: logger.debug('Host retention data: %s', data_to_save['hosts'][host]) self.backend.post('alignakretention', data=data_to_save['hosts'][host]) except BackendException as exp: # pragma: no cover - should not happen logger.error('Post alignakretention error') logger.error('Response: %s', exp.response) logger.exception("Exception: %s", exp) self.backend_connected = False return False logger.info('%d hosts saved in retention', len(data_to_save['hosts'])) self.statsmgr.counter('retention-save.hosts', len(data_to_save['hosts'])) logger.info('%d services saved in retention', len(data_to_save['services'])) self.statsmgr.counter('retention-save.services', len(data_to_save['services'])) self.statsmgr.timer('retention-save.time', time.time() - start_time) now = time.time() logger.info("Retention saved in %s seconds", (now - start_time)) except BackendException: self.backend_connected = False self.backend_errors_count += 1 logger.warning( "Alignak backend connection fails. Check and fix your configuration" ) return False return True
def test_04_login(self): """ Test with right username / password :return: None """ print('') print('test accepted connection with username/password') # Create client API backend = Backend(self.backend_address) print('Login ...') assert backend.login('admin', 'admin') print('authenticated:', backend.authenticated) print('token:', backend.token) assert_true(backend.authenticated) print('Logout ...') backend.logout() print('authenticated:', backend.authenticated) print('token:', backend.token) assert_false(backend.authenticated) print('Login ...') print('authenticated:', backend.authenticated) assert backend.login('admin', 'admin') print('authenticated:', backend.authenticated) print('token:', backend.token) assert_true(backend.authenticated) print('Logout ...') backend.logout() print('authenticated:', backend.authenticated) print('token:', backend.token) assert_false(backend.authenticated) print('Logout ...') backend.logout() print('authenticated:', backend.authenticated) print('token:', backend.token) assert_false(backend.authenticated) print('get object ... must be refused!') with assert_raises(BackendException) as cm: backend.get('host') ex = cm.exception print('exception:', str(ex.code)) assert_true(ex.code == 1001, str(ex)) print('get_all object ... must be refused!') with assert_raises(BackendException) as cm: backend.get_all('host') ex = cm.exception print('exception:', str(ex.code)) assert_true(ex.code == 1001, str(ex)) print('get all domains ... must be refused!') with assert_raises(BackendException) as cm: backend.get_domains() ex = cm.exception print('exception:', str(ex.code)) assert_true(ex.code == 1001, str(ex)) print('post data ... must be refused!') with assert_raises(BackendException) as cm: data = {'fake': 'fake'} backend.post('user', data=data) ex = cm.exception print('exception:', str(ex.code)) assert_true(ex.code == 1001, str(ex)) print('patch data ... must be refused!') with assert_raises(BackendException) as cm: data = {'fake': 'fake'} headers = {'If-Match': ''} backend.patch('user', data=data, headers=headers) ex = cm.exception print('exception:', str(ex.code)) assert_true(ex.code == 1001, str(ex)) print('delete data ... must be refused!') with assert_raises(BackendException) as cm: headers = {'If-Match': ''} backend.delete('user', headers=headers) ex = cm.exception print('exception:', str(ex.code)) assert_true(ex.code == 1001, str(ex))
class FrontEnd(object): """ Frontend class used to communicate with alignak-backend """ last_livestate_hosts = None last_livestate_services = None def __init__(self): """ Initialize class object ... """ self.url_endpoint_root = None self.backend = None self.connected = False self.initialized = False self.authenticated = False self.logged_in = None self.token = None # Backend objects which will be loaded on backend connection ... # ... do not need to wait for any user request to get these objects self.interested_in = ['contact', 'uipref'] # Backend available objects (filled with objects received from backend) self.backend_available_objets = [] # Frontend objects self.objects_cache = {} # API Data model self.dm_server_name = None self.dm_api_name = None self.dm_base = None self.dm_domains = {} def configure(self, endpoint): """ Initialize backend connection ... """ # Backend API start point if endpoint.endswith('/'): # pragma: no cover - test url is complying ... self.url_endpoint_root = endpoint[0:-1] else: self.url_endpoint_root = endpoint logger.info("backend endpoint: %s", self.url_endpoint_root) self.backend = Backend(self.url_endpoint_root) def login(self, username=None, password=None, token=None, force=False): """ Authenticate user credentials against backend :param username: user to authenticate :type username: string :param password: password :type password: string :return: user token if authenticated, else None :rtype: string """ try: if self.authenticated and self.token and not force: return self.token if token: try: # Test the backend connection self.backend.token = token logger.info("request backend user authentication, token: %s", token) self.backend_available_objets = self.backend.get_domains() if self.backend_available_objets: self.authenticated = True self.token = token logger.info("backend user authenticated") except BackendException as e: logger.error("frontend connection, error: %s", str(e)) self.authenticated = False self.token = None else: self.authenticated = False self.token = None logger.info("request backend user authentication, username: %s", username) self.authenticated = self.backend.login(username=username, password=password) if self.authenticated: self.token = self.backend.token logger.info("backend user authenticated: %s", username) except BackendException as e: logger.error("backend login, error: %s", str(e)) logger.debug("exception type: %s", type(e)) logger.debug("Back trace of this kill: %s", traceback.format_exc()) self.connected = False self.authenticated = self.connected raise e return self.token def logout(self): """ Logout user from backend :return: True :rtype: boolean """ self.connected = False self.authenticated = False self.token = None return self.backend.logout() def connect(self, username=None, token=None): """ If backend connection is available: - retrieves all managed domains on the root endpoint - get the backend schema (models) - load some persistent elements (defined on init) - find the contact associated with the current logged in user :param username: authenticated user :type username: string :param token: user token :type token: string :return: true / false :rtype: boolean """ try: self.connected = False matching_contact = False # Backend authentication ... if not self.authenticated: return self.connected # Connect the backend self.backend_available_objets = self.backend.get_domains() if self.backend_available_objets: self.connected = True if self.connected: # Retrieve data model from the backend response = requests.get('/'.join([self.url_endpoint_root, 'docs/spec.json'])) resp = response.json() self.dm_server_name = resp['server_name'] self.dm_api_name = resp['api_name'] self.dm_base = resp['base'] self.dm_domains = {} for domain_name in resp['domains']: fields = resp['domains'][domain_name]["/" + domain_name]['POST']['params'] self.dm_domains.update({ domain_name: fields }) # Initialize embedded objects if not self.initialized: self.initialize() # If we got contacts, try to find a contact matching our authenticated user ... if 'contact' in self.objects_cache: for contact in self.objects_cache['contact']: logger.debug( "available contact: %s / %s", contact["contact_name"], contact["token"] ) if (token and contact["token"] == token) or ( username and contact["contact_name"] == username): matching_contact = True self.logged_in = contact self.token = contact["token"] logger.info( "found a contact matching logged in user contact: %s (%s)", contact["contact_name"], contact["token"] ) self.connected = matching_contact self.authenticated = self.connected except BackendException as e: logger.error("frontend connection, error: %s", str(e)) self.connected = False self.authenticated = self.connected raise e except Exception as e: # pragma: no cover - simple protection if ever happened ... logger.error("frontend connection, error: %s", str(e)) logger.debug("exception type: %s", type(e)) logger.debug("Back trace of this kill: %s", traceback.format_exc()) return self.connected def disconnect(self): """ Disconnect backend :return: True :rtype: boolean """ self.connected = False self.authenticated = False self.token = None self.logged_in = None self.backend.logout() return True def initialize(self): """ Initialize self cached backend objects. Load the backend that we will keep in our cache. :return: true / false :rtype: boolean """ try: self.initialized = False # Connect to backend if not yet connected ... if not self.connected: # pragma: no cover - simple protection if ever happened ... self.connected = self.connect() if not self.connected: # pragma: no cover - simple protection if ever happened ... return False # Fetch only objects type which I am interested in ... for object_type in self.backend_available_objets: t = object_type["href"] if t in self.interested_in: logger.info( "getting '%s' cached objects on %s%s", object_type["title"], self.url_endpoint_root, t ) # Get all objects of type t ... items = self.get_objects(t, None, True) self.objects_cache[t] = items self.initialized = True except Exception as e: # pragma: no cover logger.error("FrontEnd, initialize, exception: %s", str(e)) return self.initialized def get_objects(self, object_type, parameters=None, all_elements=False): """ Get stored objects !!! NOTE !!! Beware of the all_elements=True parameter because the backend client method fetches all the elements and the get_objects is not able anymore to send the _meta information ! :param object_type: object type (eg. host, contact, ...) :type object_type: str :param parameters: list of parameters for the backend API :type parameters: list :param all_elements: get all elements (True) or apply default pagination :type all_elements: bool :return: list of properties when query item | list of items when get many items :rtype: list """ try: items = [] logger.info( "get_objects, type: %s, parameters: %s / %d (%s)", object_type, parameters, all_elements, self.token ) # If requested objects are stored locally ... if object_type in self.objects_cache: logger.debug("get_objects, returns local store objects") return {'_items': self.objects_cache[object_type]} # Request objects from the backend ... if all_elements: items = self.backend.get_all(object_type, parameters) else: items = self.backend.get(object_type, parameters) # logger.debug("get_objects, type: %s, items: %s", object_type, items) # Should be handled in the try / except ... but exception is not always raised! if '_error' in items: # pragma: no cover - need specific backend tests error = items['_error'] logger.error( "backend get: %s, %s", error['code'], error['message'] ) items = [] except Exception as e: # pragma: no cover - need specific backend tests logger.error("get_objects, exception: %s", str(e)) return items def set_user_preferences(self, user, prefs_type, parameters): """ Set user's preferences An exception is raised if an error occurs, else returns the backend response :param user: username :type user: str :param prefs_type: preference type :type prefs_type: str :param parameters: list of parameters for the backend API :type parameters: list :return: server's response :rtype: dict """ try: response = None logger.debug( "set_user_preferences, type: %s, for: %s, parameters: %s", prefs_type, user, parameters ) # Still existing ... items = self.backend.get_all( 'uipref', params={'where': '{"type":"%s", "user": "******"}' % (prefs_type, user)} ) if items: items = items[0] logger.info( "set_user_preferences, update exising record: %s / %s (%s)", prefs_type, user, items['_id'] ) # Update existing record ... headers = {'If-Match': items['_etag']} data = { "user": user, "type": prefs_type, "data": parameters } response = self.backend.patch( '/'.join(['uipref', items['_id']]), data=data, headers=headers, inception=True ) else: # Create new record ... logger.info( "set_user_preferences, create new record: %s / %s", prefs_type, user ) data = { "user": user, "type": prefs_type, "data": parameters } response = self.backend.post('uipref', data=data) logger.debug("set_user_preferences, response: %s", response) except Exception as e: # pragma: no cover - need specific backend tests logger.error("set_user_preferences, exception: %s", str(e)) raise e return response def get_user_preferences(self, user, prefs_type): """ Get user's preferences If the data are not found, returns None else return found data. :param user: username :type user: str :param prefs_type: preference type :type prefs_type: str :return: found data, or None :rtype: dict """ try: logger.debug("get_user_preferences, type: %s, for: %s", prefs_type, user) # Still existing ... items = self.backend.get_all( 'uipref', params={'where': '{"type":"%s", "user": "******"}' % (prefs_type, user)} ) if items: logger.debug("get_user_preferences, found: %s", items[0]) return items[0] except Exception as e: # pragma: no cover - need specific backend tests logger.error("get_user_preferences, exception: %s", str(e)) raise e return None # pragma: no cover - need specific backend tests def get_ui_data_model(self, element_type): """ Get the data model for an element type If the data model specifies that the element is managed in the UI, all the fields for this element are provided :param element_type: element type :type element_type: str :return: list of fields name/title :rtype: list """ logger.debug("get_ui_data_model, element type: %s", element_type) logger.debug("get_ui_data_model, domains: %s", self.dm_domains) fields = [] if element_type in self.dm_domains: for model in self.dm_domains[element_type]: logger.debug("get_ui_data_model, model: %s", model["name"]) # element is considered for the UI if 'ui' in model["name"]: fields = self.dm_domains[element_type] break return fields def get_livestate(self, parameters=None, all_elements=True): """ Get livestate for hosts and services :param parameters: backend request parameters :type parameters: dic :param all_elements: get all elements (True) or apply default pagination :type all_elements: bool :return: list of hosts/services live states :rtype: list """ return self.get_objects('livestate', parameters, all_elements=all_elements) def get_livestate_hosts(self, parameters=None, all_elements=True): """ Get livestate for hosts Elements in the livestat which service_description is null (eg. hosts) :param parameters: backend request parameters :type parameters: dic :param all_elements: get all elements (True) or apply default pagination :type all_elements: bool :return: list of hosts live states :rtype: list """ if not parameters: parameters = {} parameters.update({'where': '{"service_description": null}'}) return self.get_objects('livestate', parameters, all_elements=all_elements) def get_livestate_services(self, parameters=None, all_elements=True): """ Get livestate for services Elements in the livestat which service_description is not null (eg. services) :param parameters: backend request parameters :type parameters: dic :param all_elements: get all elements (True) or apply default pagination :type all_elements: bool :return: list of services live states :rtype: list """ if not parameters: parameters = {} parameters.update({'where': '{"service_description": {"$ne": null}}'}) return self.get_objects('livestate', parameters, all_elements=all_elements) def get_livesynthesis(self, parameters=None, all_elements=True): """ Get livestate synthesis for hosts and services Example backend response : { "hosts_total": 12, "hosts_business_impact": 0, "hosts_acknowledged": 0, "hosts_in_downtime": 0, "hosts_flapping": 0, "hosts_up_hard": 0, "hosts_up_soft": 0, "hosts_unreachable_hard": 0, "hosts_unreachable_soft": 0, "hosts_down_hard": 0, "hosts_down_soft": 0, "services_total": 245, "services_business_impact": 0, "services_acknowledged": 0, "services_in_downtime": 0, "services_flapping": 0, "services_ok_hard": 0, "services_ok_soft": 0, "services_warning_hard": 0, "services_warning_soft": 0, "services_critical_hard": 0, "services_critical_soft": 0, "services_unknown_soft": 0, "services_unknown_hard": 0, "_created": "Thu, 01 Jan 1970 00:00:00 GMT", "_updated": "Sat, 10 Oct 2015 09:08:59 GMT", "_id": "5618d5abf9e3852e3444a5ee", "_etag": "edce4629fff2412ab7257216bb66c54795baada4" "_links": { "self": { "href": "livesynthesis/5618d5abf9e3852e3444a5ee", "title": "Livesynthesi" } }, } Returns an hosts_synthesis dictionary containing: - number of elements - business impact - count for each state (hard and soft) - percentage for each state (hard and soft) - number of problems (down and unreachable, only hard state) - percentage of problems Returns a services_synthesis dictionary containing: - number of elements - business impact - count for each state (hard and soft) - percentage for each state (hard and soft) - number of problems (down and unreachable, only hard state) - percentage of problems :return: hosts and services live state synthesis in a dictionary :rtype: dict """ ls = self.get_objects('livesynthesis', parameters, all_elements=all_elements) if not ls: return None ls = ls[0] # Services synthesis hosts_synthesis = { 'nb_elts': ls["hosts_total"], 'business_impact': ls["hosts_business_impact"], } for state in 'up', 'down', 'unreachable': hosts_synthesis.update({ "nb_" + state: ls["hosts_%s_hard" % state] + ls["hosts_%s_soft" % state] }) for state in 'acknowledged', 'in_downtime', 'flapping': hosts_synthesis.update({ "nb_" + state: ls["hosts_%s" % state] }) hosts_synthesis.update({ "nb_problems": ls["hosts_down_hard"] + ls["hosts_unreachable_hard"] }) for state in 'up', 'down', 'unreachable': hosts_synthesis.update({ "pct_" + state: round( 100.0 * hosts_synthesis['nb_' + state] / hosts_synthesis['nb_elts'], 2 ) if hosts_synthesis['nb_elts'] else 0.0 }) for state in 'acknowledged', 'in_downtime', 'flapping', 'problems': hosts_synthesis.update({ "pct_" + state: round( 100.0 * hosts_synthesis['nb_' + state] / hosts_synthesis['nb_elts'], 2 ) if hosts_synthesis['nb_elts'] else 0.0 }) # Services synthesis services_synthesis = { 'nb_elts': ls["services_total"], 'business_impact': ls["services_business_impact"], } for state in 'ok', 'warning', 'critical', 'unknown': services_synthesis.update({ "nb_" + state: ls["services_%s_hard" % state] + ls["services_%s_soft" % state] }) for state in 'acknowledged', 'in_downtime', 'flapping': services_synthesis.update({ "nb_" + state: ls["services_%s" % state] }) services_synthesis.update({ "nb_problems": ls["services_warning_hard"] + ls["services_critical_hard"] }) for state in 'ok', 'warning', 'critical', 'unknown': services_synthesis.update({ "pct_" + state: round( 100.0 * services_synthesis['nb_' + state] / services_synthesis['nb_elts'], 2 ) if services_synthesis['nb_elts'] else 0.0 }) for state in 'acknowledged', 'in_downtime', 'flapping', 'problems': services_synthesis.update({ "pct_" + state: round( 100.0 * services_synthesis['nb_' + state] / services_synthesis['nb_elts'], 2 ) if services_synthesis['nb_elts'] else 0.0 }) synthesis = { 'hosts_synthesis': hosts_synthesis, 'services_synthesis': services_synthesis } return synthesis def get_hosts_synthesis(self): """ Deprecated function ! Add an API endpoint /hosts_synthesis -------------------------------------------------------------------------------------------- Returns an hosts live state synthesis containing: {'nb_down': 4, 'pct_up': 66.67, 'pct_down': 33.33, 'nb_unreachable': 0, 'nb_unknown': 0, 'pct_problems': 33.33, 'nb_downtime': 0, 'nb_problems': 4, 'bi': 3, 'pct_unknown': 0.0, 'nb_ack': 0, 'nb_elts': 12, 'nb_up': 8, 'nb_pending': 0, 'pct_ack': 0.0, 'pct_pending': 0.0, 'pct_downtime': 0.0, 'pct_unreachable': 0.0} Returns none if no hosts are available :return: hosts live state synthesis :rtype: dict """ parameters = {"embedded": '{"host_name":1}'} hosts = self.get_livestate_hosts(parameters=parameters) if not hosts: return None h = dict() h['nb_elts'] = len(hosts) h['bi'] = max(int(i['host_name']['business_impact']) for i in hosts if 'business_impact' in i['host_name']) for state in 'up', 'down', 'unreachable', 'pending': h[state] = [i for i in hosts if i['state'] == state.upper()] h['unknown'] = [i for i in hosts if i['state'].lower() not in ['up', 'down', 'unreachable', 'pending']] h['ack'] = [i for i in hosts if i['state'] not in ['UP', 'PENDING'] and ('acknowledged' in i and i['acknowledged'])] h['downtime'] = [i for i in hosts if ('in_scheduled_downtime' in i and i['in_scheduled_downtime'])] for state in 'up', 'down', 'unreachable', 'pending', 'unknown', 'ack', 'downtime': h['nb_' + state] = len(h[state]) h['pct_' + state] = 0 if hosts: h['pct_' + state] = round(100.0 * h['nb_' + state] / h['nb_elts'], 2) del h[state] h['nb_problems'] = h['nb_down'] + h['nb_unreachable'] + h['nb_unknown'] h['pct_problems'] = 0 if hosts: h['pct_problems'] = round(100.0 * h['nb_problems'] / h['nb_elts'], 2) logger.info("get_hosts_synthesis: %s, %s", type(h), h) return h def get_services_synthesis(self): """ Deprecated function ! Add an API endpoint /services_synthesis -------------------------------------------------------------------------------------------- Returns a services live state synthesis containing: {'nb_critical': 4, 'pct_ok': 66.67, 'pct_critical': 33.33, 'nb_warning': 0, 'nb_unknown': 0, 'pct_problems': 33.33, 'nb_downtime': 0, 'nb_problems': 4, 'bi': 3, 'pct_unknown': 0.0, 'nb_ack': 0, 'nb_elts': 12, 'nb_ok': 8, 'nb_pending': 0, 'pct_ack': 0.0, 'pct_pending': 0.0, 'pct_downtime': 0.0, 'pct_warning': 0.0} Returns none if no services are available :return: services live state synthesis :rtype: dict """ parameters = {"embedded": '{"service_description":1}'} services = self.get_livestate_services(parameters=parameters) if not services: return None s = dict() s['nb_elts'] = len(services) s['bi'] = max(int(i['service_description']['business_impact']) for i in services if 'business_impact' in i['service_description']) for state in 'ok', 'critical', 'warning', 'pending': s[state] = [i for i in services if i['state'] == state.upper()] s['unknown'] = [i for i in services if i['state'].lower() not in ['ok', 'critical', 'warning', 'pending']] s['ack'] = [i for i in services if i['state'] not in ['OK', 'PENDING'] and i['acknowledged']] s['downtime'] = [i for i in services if i['state'] not in ['up', 'pending'] and i['acknowledged']] for state in 'ok', 'critical', 'warning', 'unknown', 'pending', 'ack', 'downtime': s['nb_' + state] = len(s[state]) s['pct_' + state] = 0 if services: s['pct_' + state] = round(100.0 * s['nb_' + state] / s['nb_elts'], 2) del s[state] s['nb_problems'] = s['nb_warning'] + s['nb_critical'] + s['nb_unknown'] s['pct_problems'] = 0 if services: s['pct_problems'] = round(100.0 * s['nb_problems'] / s['nb_elts'], 2) logger.info("get_services_synthesis: %s", s) return s
class BackendClient(object): """ Class who collect informations with Backend-Client and returns data for Alignak-App. """ connection_status = { True: 'Success', False: 'Failure' } def __init__(self): self.backend = None self.connected = False self.user = {} self.ws_client = WSClient() def login(self, username=None, password=None, proxies=None, check=False): """ Connect to alignak backend :param username: name or token of user :type username: str :param password: password of user. If token given, this parameter is useless :type password: str :param proxies: dictionnary for proxy :type proxies: dict :param check: define if login is a check or a first login :type check: bool :return: True if connected or False if not :rtype: bool """ # Credentials if not username and not password: if 'token' in self.user: username = self.user['token'] # Create Backend object backend_url = settings.get_config('Alignak', 'backend') processes = int(settings.get_config('Alignak', 'processes')) self.backend = Backend(backend_url, processes=processes) logger.debug('Backend URL : %s', backend_url) if not check: logger.info('Try to connect to the Alignak backend...') if username and password: # Username & password : not recommended, without login QDialog try: self.connected = self.backend.login(username, password, proxies=proxies) if self.connected: self.user['username'] = username self.user['token'] = self.backend.token logger.info('Connection by password: %s', self.connection_status[self.connected]) except BackendException: # pragma: no cover logger.error('Connection to Backend has failed !') elif username and not password: # Username as token : recommended if 'token' in self.user: self.backend.set_token(self.user['token']) else: self.backend.set_token(username) self.user['token'] = username # Make backend connected to test token self.connected = True connection_test = self.get('user', {'projection': json.dumps({'name': 1})}) self.connected = bool(connection_test) if not check: logger.info('Connection by token: %s', self.connection_status[self.connected]) else: logger.warning( 'Connection to Backend has failed.\n' 'Check [Alignak] section in configuration file or use login window of application.' ) if self.connected and not check: if settings.get_config('Alignak', 'webservice'): self.ws_client.login(self.user['token']) else: logger.info('No configured Web Service.') return self.connected def get(self, endpoint, params=None, projection=None, all_items=False): """ GET on alignak Backend REST API :param endpoint: endpoint (API URL) :type endpoint: str :param params: dict of parameters for the app_backend API :type params: dict|None :param projection: list of field to get, if None, get all :type projection: list|None :param all_items: make GET on all items :type all_items: bool :return: request response :rtype: dict """ request = None if self.connected: if params is None: params = {'max_results': 50} if projection is not None: generate_proj = {} for field in projection: generate_proj[field] = 1 params['projection'] = json.dumps(generate_proj) # Request try: if not all_items: request = self.backend.get( endpoint, params ) else: request = self.backend.get_all( endpoint, params ) logger.info('GET on [%s] backend > %s', endpoint, str(request['_status'])) logger.debug('\tparams: [%s]', str(params)) except BackendException: self.connected = False else: logger.info('App is not connected to backend !') return request def post(self, endpoint, data, headers=None): # pragma: no cover - Post already test by client """ POST on alignak Backend REST API :param endpoint: endpoint (API URL) :type endpoint: str :param data: properties of item to create | add :type data: dict :param headers: headers (example: Content-Type) :type headers: dict|None :return: response (creation information) :rtype: dict """ request = None if self.connected: try: request = self.backend.post(endpoint, data, headers=headers) logger.info('POST on [%s] backend > %s', endpoint, str(request['_status'])) logger.debug('\tdata: [%s]', str(data)) logger.debug('\theaders: [%s]', str(headers)) except BackendException: self.connected = False else: logger.info('App is not connected to backend !') return request def patch(self, endpoint, data, headers): """ PATCH on alignak Backend REST API :param endpoint: endpoint (API URL) :type endpoint: str :param data: properties of item to update :type data: dict :param headers: headers (example: Content-Type). 'If-Match' required :type headers: dict :return: dictionary containing patch response from the backend :rtype: dict """ request = None if self.connected: try: request = self.backend.patch(endpoint, data, headers=headers, inception=True) logger.info('PATCH on [%s] backend > %s', endpoint, str(request['_status'])) logger.debug('\tdata: [%s]', str(data)) logger.debug('\theaders: [%s]', str(headers)) except BackendException: self.connected = False else: logger.info('App is not connected to backend !') return request def acknowledge(self, item, sticky, notify, comment): # pragma: no cover """ Prepare data for acknowledge and POST on backend API or WS if available :param item: item to acknowledge: host | service :type item: alignak_app.items.host.Host | alignak_app.items.service.Service :param sticky: define if ack is sticky or not :type sticky: bool :param notify: define if ack should notify user or not :type notify: bool :param comment: comment of ack :type comment: str :return: request response :rtype: dict """ user = data_manager.database['user'] if self.ws_client.auth: if item.item_type == 'service': command = 'ACKNOWLEDGE_SVC_PROBLEM' host = data_manager.get_item('host', '_id', item.data['host']) element = host.name else: command = 'ACKNOWLEDGE_HOST_PROBLEM' element = item.name item_name = item.name if sticky: sticky = '2' else: sticky = '1' notify = str(int(notify)) persistent = '0' parameters = ';'.join([item_name, sticky, notify, persistent, user.name, comment]) data = { 'command': command, 'element': element, 'parameters': parameters } request = self.ws_client.post('command', params=data) else: data = { 'action': 'add', 'user': user.item_id, 'comment': comment, 'notify': notify, 'sticky': sticky } if item.item_type == 'service': data['host'] = item.data['host'] data['service'] = item.item_id else: data['host'] = item.item_id data['service'] = None request = self.post('actionacknowledge', data) return request # pylint: disable=too-many-arguments def downtime(self, item, fixed, duration, start_stamp, end_stamp, comment): # pragma: no cover """ Prepare data for downtime and POST on backend API or WS if available :param item: item to downtime: host | service :type item: alignak_app.items.host.Host | alignak_app.items.service.Service :param fixed: define if donwtime is fixed or not :type fixed: bool :param duration: duration timestamp of downtime :type duration: int :param start_stamp: start timestamp of downtime :type start_stamp: int :param end_stamp: end timestamp of downtime :type end_stamp: int :param comment: comment of downtime :type comment: str :return: request response :rtype: dict """ if self.ws_client.auth: if item.item_type == 'service': host = data_manager.get_item('host', '_id', item.data['host']) element = host.name else: element = item.name fixed = str(int(fixed)) item_name = item.name trigger_id = '0' parameters = ';'.join( [item_name, str(start_stamp), str(end_stamp), fixed, trigger_id, str(duration), data_manager.database['user'].name, comment] ) data = { 'command': 'SCHEDULE_SVC_DOWNTIME' if item.item_type == 'service' else 'SCHEDULE_HOST_DOWNTIME', 'element': element, 'parameters': parameters } request = self.ws_client.post('command', params=data) else: data = { 'action': 'add', 'user': data_manager.database['user'].item_id, 'fixed': fixed, 'duration': duration, 'start_time': start_stamp, 'end_time': end_stamp, 'comment': comment, } if item.item_type == 'service': data['host'] = item.data['host'] data['service'] = item.item_id else: data['host'] = item.item_id data['service'] = None request = app_backend.post('actiondowntime', data) return request def query_realms(self): """ Launch a request on ``realm`` endpoint """ request_model = Realm.get_request_model() request = self.get( request_model['endpoint'], request_model['params'], request_model['projection'] ) if request: realms_list = [] for backend_item in request['_items']: realm = Realm() realm.create( backend_item['_id'], backend_item, backend_item['name'], ) realms_list.append(realm) if realms_list: data_manager.update_database('realm', realms_list) if 'OK' in request['_status']: data_manager.db_is_ready[request_model['endpoint']] = True def query_timeperiods(self): """ Launch a request on ``timeperiod`` endpoint """ request_model = Period.get_request_model() request = self.get( request_model['endpoint'], request_model['params'], request_model['projection'] ) if request: periods_list = [] for backend_item in request['_items']: period = Period() period.create( backend_item['_id'], backend_item, backend_item['name'], ) periods_list.append(period) if periods_list: data_manager.update_database('timeperiod', periods_list) if 'OK' in request['_status']: data_manager.db_is_ready[request_model['endpoint']] = True def query_user(self): """ Launch request on "user" endpoint. Only for current App user. """ request_model = User.get_request_model(self.backend.token) request = self.get( request_model['endpoint'], request_model['params'], request_model['projection'] ) if request: if request['_items']: user = User() user.create( request['_items'][0]['_id'], request['_items'][0], request['_items'][0]['name'] ) data_manager.update_database('user', user) if 'OK' in request['_status']: data_manager.db_is_ready[request_model['endpoint']] = True def query_hosts(self): """ Launch request on "host" endpoint, add hosts in problems if needed """ request_model = Host.get_request_model() request = self.get( request_model['endpoint'], request_model['params'], request_model['projection'], all_items=True ) if request: hosts_list = [] for backend_item in request['_items']: host = Host() host.create( backend_item['_id'], backend_item, backend_item['name'], ) hosts_list.append(host) # If host is a problem, add / update it if data_manager.is_problem('host', backend_item): if data_manager.get_item('problems', host.item_id): data_manager.update_item_data('problems', host.item_id, host.data) else: data_manager.database['problems'].append(host) data_manager.db_is_ready['problems']['host'] = True if hosts_list: data_manager.update_database('host', hosts_list) if 'OK' in request['_status']: data_manager.db_is_ready[request_model['endpoint']] = True def query_services(self, host_id=None): """ Launch request for "service" endpoint. If ``host_id`` is given, only services related to host are added / updated :param host_id: "_id" of host :type host_id: str """ request_model = Service.get_request_model(host_id) request = self.get( request_model['endpoint'], request_model['params'], request_model['projection'], all_items=True ) if request: services_list = [] for backend_item in request['_items']: service = Service() service.create( backend_item['_id'], backend_item, backend_item['name'], ) # Add / update only services of host "if host_id" if host_id: if not data_manager.get_item('service', service.item_id): logger.debug('Add item data in database[service]') data_manager.database['service'].append(service) else: data_manager.update_item_data('service', service.item_id, service.data) # If not item ID, update all database if services_list and not host_id: data_manager.update_database('service', services_list) if host_id: host = data_manager.get_item('host', '_id', host_id) if host: logger.info('Update database[service] for %s', host.name) if 'OK' in request['_status']: data_manager.db_is_ready[request_model['endpoint']] = True def query_services_problems(self, state): """ Launch requests on "service" endpoint to get items with "ls_state = state" Wanted states are: ``WARNING``, ``CRITICAL`` and ``UNKNOWN`` :param state: state of service :type state: str """ # Services services_projection = [ 'name', 'host', 'alias', 'ls_state', 'ls_output', 'ls_acknowledged', 'ls_downtimed', 'passive_checks_enabled', 'active_checks_enabled' ] params = {'where': json.dumps({'_is_template': False, 'ls_state': state})} request = self.get( 'service', params, services_projection, all_items=True ) if request: for backend_item in request['_items']: if data_manager.is_problem('service', backend_item): service = Service() service.create( backend_item['_id'], backend_item, backend_item['name'] ) if data_manager.get_item('problems', service.item_id): data_manager.update_item_data('problems', service.item_id, service.data) else: data_manager.database['problems'].append(service) # Problems state is ready data_manager.db_is_ready['problems'][state] = True logger.info("Update database[problems] for %s services...", state) def query_alignakdaemons(self): """ Launch request on "alignakdaemon" endpoint """ request_model = Daemon.get_request_model() request = self.get( request_model['endpoint'], request_model['params'], request_model['projection'], all_items=True ) if request: daemons_list = [] for backend_item in request['_items']: daemon = Daemon() daemon.create( backend_item['_id'], backend_item, backend_item['name'], ) daemons_list.append(daemon) if daemons_list: data_manager.update_database('alignakdaemon', daemons_list) if 'OK' in request['_status']: data_manager.db_is_ready[request_model['endpoint']] = True def query_livesynthesis(self): """ Launch request on "livesynthesis" endpoint """ request_model = LiveSynthesis.get_request_model() request = self.get( request_model['endpoint'], request_model['params'], request_model['projection'], all_items=True ) if request: livesynthesis = [] for backend_item in request['_items']: synthesis = LiveSynthesis() synthesis.create( backend_item['_id'], backend_item, ) livesynthesis.append(synthesis) if livesynthesis: data_manager.update_database('livesynthesis', livesynthesis) if 'OK' in request['_status']: data_manager.db_is_ready[request_model['endpoint']] = True def query_history(self, hostname=None, host_id=None): """ Launch request on "history" endpoint but only for hosts in "data_manager" :param hostname: name of host we want history :type hostname: str :param host_id: id of host for history :type host_id: str """ request_model = History.get_request_model() if hostname and host_id: request_model['params']['where'] = json.dumps({ 'host': host_id}) request_model['params']['max_results'] = 25 request = self.get( request_model['endpoint'], request_model['params'], request_model['projection'], all_items=False ) if request: logger.debug('Add / Update history for %s (%s)', hostname, host_id) if data_manager.get_item('history', host_id): data_manager.update_item_data('history', host_id, request['_items']) else: host_history = History() host_history.create( host_id, request['_items'], hostname, ) data_manager.database['history'].append(host_history) else: # pragma: no cover, too long to test history_list = [] for history in data_manager.database['history']: request_model['params']['where'] = json.dumps({ 'host': history.item_id}) request_model['params']['max_results'] = 25 request = self.get( request_model['endpoint'], request_model['params'], request_model['projection'], all_items=False ) if request: host_history = History() host_history.create( history.item_id, request['_items'], history.name, ) history_list.append(host_history) if history_list: data_manager.update_database('history', history_list) def query_notifications(self): # pragma: no cover, notifications can be empty """ Launch request on "history" endpoint. Only for 'type': 'monitoring.notification' and for current App user """ request_model = Event.get_request_model() request = self.get( request_model['endpoint'], request_model['params'], request_model['projection'], all_items=False ) if request: notifications = [] for backend_item in request['_items']: message_split = backend_item['message'].split(';') user = message_split[0].split(':')[1].strip() if 'imported_admin' in user: user = '******' if user == data_manager.database['user'].name: notification = Event() notification.create( backend_item['_id'], backend_item, ) notifications.append(notification) if notifications: data_manager.update_database('notifications', notifications) def get_backend_status_icon(self): """ Return backend status icon name :return: status icon name :rtype: str """ if self.connected: return Daemon.get_states('ok') return Daemon.get_states('ko') def get_ws_status_icon(self): """ Return Web Service status icon name :return: status icon name :rtype: str """ if self.ws_client.auth: return Daemon.get_states('ok') return Daemon.get_states('ko')
class AppBackend(object): """ Class who collect informations with Backend-Client and returns data for Alignak-App. """ def __init__(self): self.backend = None self.user = {} self.connected = False self.app = None def login(self, username=None, password=None): """ Connect to app_backend with credentials in settings.cfg. :return: True if connected or False if not :rtype: bool """ connect = False # Credentials if not username and not password: if self.user: username = self.user['token'] else: username = get_app_config('Alignak', 'username') password = get_app_config('Alignak', 'password') # Create Backend object backend_url = get_app_config('Alignak', 'backend') processes = int(get_app_config('Alignak', 'processes')) self.backend = Backend(backend_url, processes=processes) logger.debug('Backend URL : %s', backend_url) logger.info('Try to connect to app_backend...') if username and password: # Username & password : not recommended, without "widgets.login.py" form. try: connect = self.backend.login(username, password) if connect: self.user['username'] = username self.user['token'] = self.backend.token logger.info('Connection by password: %s', str(connect)) except BackendException as e: # pragma: no cover logger.error('Connection to Backend has failed: %s', str(e)) elif username and not password: # Username as token : recommended self.backend.authenticated = True if self.user: self.backend.token = self.user['token'] else: self.backend.token = username self.user['token'] = username # Test to check token self.connected = True connect = bool(self.get('livesynthesis')) logger.info('Connection by token: %s', str(connect)) else: # Else exit logger.error( 'Connection to Backend has failed.\nCheck [Backend] section in configuration file.' ) connect = False self.connected = connect return connect def get(self, endpoint, params=None, projection=None): """ GET on alignak Backend REST API. :param endpoint: endpoint (API URL) :type endpoint: str :param params: dict of parameters for the app_backend API :type params: dict|None :param projection: list of field to get, if None, get all :type projection: list|None :return desired request of app_backend :rtype: dict """ request = None if params is None: params = {'max_results': 50} if projection is not None: generate_proj = {} for field in projection: generate_proj[field] = 1 params['projection'] = json.dumps(generate_proj) if self.connected: # Request try: request = self.backend.get_all(endpoint, params) logger.debug('GET: %s', endpoint) logger.debug('..with params: %s', str(params)) logger.debug('...Response > %s', str(request['_status'])) except BackendException as e: logger.error('GET failed: %s', str(e)) logger.warning( 'Application will check the connection with Backend...') self.connected = False if not self.app.reconnect_mode: self.app.reconnecting.emit(self, str(e)) return request return request def post(self, endpoint, data, headers=None): # pragma: no cover - Post already test by client """ POST on alignak Backend REST API :param endpoint: endpoint (API URL) :type endpoint: str :param data: properties of item to create | add :type data: dict :param headers: headers (example: Content-Type) :type headers: dict|None :return: response (creation information) :rtype: dict """ resp = None if self.connected: try: resp = self.backend.post(endpoint, data, headers=headers) logger.debug('POST on %s', endpoint) logger.debug('..with data: %s', str(data)) logger.debug('...Response > %s', str(resp)) except BackendException as e: logger.error('POST failed: %s', str(e)) logger.warning( 'Application will check the connection with Backend...') self.connected = False if not self.app.reconnect_mode: self.app.reconnecting.emit(self, str(e)) return resp return resp def get_host(self, key, value, projection=None): """ Return the host corresponding to "key"/"value" pair :param key: key corresponding to value :type key: str :param value: value of key :type value: str :param projection: list of field to get, if None, get all :type projection: list|None :return: None if not found or item dict :rtype: dict|None """ params = {'where': json.dumps({'_is_template': False, key: value})} hosts = self.get('host', params, projection=projection) if hosts and len(hosts['_items']) > 0: # pylint: disable=len-as-condition wanted_host = hosts['_items'][0] else: wanted_host = None return wanted_host def get_service(self, host_id, service_id, projection=None): """ Returns the desired service of the specified host :param host_id: "_id" of host :type host_id: str :param service_id: "_id" of wanted service :type service_id: str :param projection: list of field to get, if None, get all :type projection: list|None :return: wanted service :rtype: dict """ params = { 'where': json.dumps({ '_is_template': False, 'host': host_id }) } services = self.get('service', params=params, projection=projection) wanted_service = None if services: if len(services['_items']) > 0: # pylint: disable=len-as-condition wanted_service = services['_items'][0] for service in services['_items']: if service['_id'] == service_id: wanted_service = service return wanted_service def get_host_with_services(self, host_name): """ Returns the desired host and all its services :param host_name: desired host :type host_name: str :return dict with host data and its associated services :rtype: dict """ host_data = None host_projection = [ 'name', 'alias', 'ls_state', '_id', 'ls_acknowledged', 'ls_downtimed', 'ls_last_check', 'ls_output', 'address', 'business_impact', 'parents', 'ls_last_state_changed' ] host = self.get_host('name', host_name, projection=host_projection) if host: params = { 'where': json.dumps({ '_is_template': False, 'host': host['_id'] }) } service_projection = [ 'name', 'alias', 'display_name', 'ls_state', 'ls_acknowledged', 'ls_downtimed', 'ls_last_check', 'ls_output', 'business_impact', 'customs', '_overall_state_id', 'aggregation', 'ls_last_state_changed' ] services = self.get('service', params=params, projection=service_projection) services_host = services['_items'] host_data = {'host': host, 'services': services_host} return host_data def get_user(self, projection=None): """ Get current user. The token must already be acquired :param projection: list of field to get, if None, get all :type projection: list|None :return user items :rtype dict|None """ params = {'where': json.dumps({'token': self.user['token']})} if projection is not None: generate_proj = {} for field in projection: generate_proj[field] = 1 params['projection'] = json.dumps(generate_proj) user = self.get('user', params, projection=projection) if user: return user['_items'][0] return None def synthesis_count(self): """ Get on "synthesis" endpoint and return the states of hosts and services :return: states of hosts and services. :rtype: dict """ states = { 'hosts': { 'up': 0, 'down': 0, 'unreachable': 0, 'acknowledge': 0, 'downtime': 0 }, 'services': { 'ok': 0, 'critical': 0, 'unknown': 0, 'warning': 0, 'unreachable': 0, 'acknowledge': 0, 'downtime': 0 } } live_synthesis = self.get('livesynthesis') if live_synthesis: for realm in live_synthesis['_items']: states['hosts']['up'] += realm['hosts_up_soft'] states['hosts']['up'] += realm['hosts_up_hard'] states['hosts']['unreachable'] += realm[ 'hosts_unreachable_soft'] states['hosts']['unreachable'] += realm[ 'hosts_unreachable_hard'] states['hosts']['down'] += realm['hosts_down_soft'] states['hosts']['down'] += realm['hosts_down_hard'] states['hosts']['acknowledge'] += realm['hosts_acknowledged'] states['hosts']['downtime'] += realm['hosts_in_downtime'] states['services']['ok'] += realm['services_ok_soft'] states['services']['ok'] += realm['services_ok_hard'] states['services']['warning'] += realm['services_warning_soft'] states['services']['warning'] += realm['services_warning_hard'] states['services']['critical'] += realm[ 'services_critical_soft'] states['services']['critical'] += realm[ 'services_critical_hard'] states['services']['unknown'] += realm['services_unknown_soft'] states['services']['unknown'] += realm['services_unknown_hard'] states['services']['unreachable'] += realm[ 'services_unreachable_soft'] states['services']['unreachable'] += realm[ 'services_unreachable_hard'] states['services']['acknowledge'] += realm[ 'services_acknowledged'] states['services']['downtime'] += realm['services_in_downtime'] logger.info('Store current states...') return states
class BackendClient(object): """ Class who collect informations with Backend-Client and returns data for Alignak-App. """ connection_status = {True: 'Success', False: 'Failure'} def __init__(self): self.backend = None self.connected = False self.user = {} self.ws_client = WSClient() def login(self, username=None, password=None, proxies=None, check=False): """ Connect to alignak backend :param username: name or token of user :type username: str :param password: password of user. If token given, this parameter is useless :type password: str :param proxies: dictionnary for proxy :type proxies: dict :param check: define if login is a check or a first login :type check: bool :return: True if connected or False if not :rtype: bool """ # Credentials if not username and not password: if 'token' in self.user: username = self.user['token'] # Create Backend object backend_url = settings.get_config('Alignak', 'backend') processes = int(settings.get_config('Alignak', 'processes')) self.backend = Backend(backend_url, processes=processes) logger.debug('Backend URL : %s', backend_url) if not check: logger.info('Try to connect to the Alignak backend...') if username and password: # Username & password : not recommended, without login QDialog try: self.connected = self.backend.login(username, password, proxies=proxies) if self.connected: self.user['username'] = username self.user['token'] = self.backend.token logger.info('Connection by password: %s', self.connection_status[self.connected]) except BackendException: # pragma: no cover logger.error('Connection to Backend has failed !') elif username and not password: # Username as token : recommended if 'token' in self.user: self.backend.set_token(self.user['token']) else: self.backend.set_token(username) self.user['token'] = username # Make backend connected to test token self.connected = True connection_test = self.get('user', {'projection': json.dumps({'name': 1})}) self.connected = bool(connection_test) if not check: logger.info('Connection by token: %s', self.connection_status[self.connected]) else: logger.warning( 'Connection to Backend has failed.\n' 'Check [Alignak] section in configuration file or use login window of application.' ) if self.connected and not check: if settings.get_config('Alignak', 'webservice'): self.ws_client.login(self.user['token']) else: logger.info('No configured Web Service.') return self.connected def get(self, endpoint, params=None, projection=None, all_items=False): """ GET on alignak Backend REST API :param endpoint: endpoint (API URL) :type endpoint: str :param params: dict of parameters for the app_backend API :type params: dict|None :param projection: list of field to get, if None, get all :type projection: list|None :param all_items: make GET on all items :type all_items: bool :return: request response :rtype: dict """ request = None if self.connected: if params is None: params = {'max_results': 50} if projection is not None: generate_proj = {} for field in projection: generate_proj[field] = 1 params['projection'] = json.dumps(generate_proj) # Request try: if not all_items: request = self.backend.get(endpoint, params) else: request = self.backend.get_all(endpoint, params) logger.info('GET on [%s] backend > %s', endpoint, str(request['_status'])) logger.debug('\tparams: [%s]', str(params)) except BackendException: self.connected = False else: logger.info('App is not connected to backend !') return request def post(self, endpoint, data, headers=None): # pragma: no cover - Post already test by client """ POST on alignak Backend REST API :param endpoint: endpoint (API URL) :type endpoint: str :param data: properties of item to create | add :type data: dict :param headers: headers (example: Content-Type) :type headers: dict|None :return: response (creation information) :rtype: dict """ request = None if self.connected: try: request = self.backend.post(endpoint, data, headers=headers) logger.info('POST on [%s] backend > %s', endpoint, str(request['_status'])) logger.debug('\tdata: [%s]', str(data)) logger.debug('\theaders: [%s]', str(headers)) except BackendException: self.connected = False else: logger.info('App is not connected to backend !') return request def patch(self, endpoint, data, headers): """ PATCH on alignak Backend REST API :param endpoint: endpoint (API URL) :type endpoint: str :param data: properties of item to update :type data: dict :param headers: headers (example: Content-Type). 'If-Match' required :type headers: dict :return: dictionary containing patch response from the backend :rtype: dict """ request = None if self.connected: try: request = self.backend.patch(endpoint, data, headers=headers, inception=True) logger.info('PATCH on [%s] backend > %s', endpoint, str(request['_status'])) logger.debug('\tdata: [%s]', str(data)) logger.debug('\theaders: [%s]', str(headers)) except BackendException: self.connected = False else: logger.info('App is not connected to backend !') return request def acknowledge(self, item, sticky, notify, comment): # pragma: no cover """ Prepare data for acknowledge and POST on backend API or WS if available :param item: item to acknowledge: host | service :type item: alignak_app.items.host.Host | alignak_app.items.service.Service :param sticky: define if ack is sticky or not :type sticky: bool :param notify: define if ack should notify user or not :type notify: bool :param comment: comment of ack :type comment: str :return: request response :rtype: dict """ user = data_manager.database['user'] if self.ws_client.auth: if item.item_type == 'service': command = 'ACKNOWLEDGE_SVC_PROBLEM' host = data_manager.get_item('host', '_id', item.data['host']) element = host.name else: command = 'ACKNOWLEDGE_HOST_PROBLEM' element = item.name item_name = item.name if sticky: sticky = '2' else: sticky = '1' notify = str(int(notify)) persistent = '0' parameters = ';'.join( [item_name, sticky, notify, persistent, user.name, comment]) data = { 'command': command, 'element': element, 'parameters': parameters } request = self.ws_client.post('command', params=data) else: data = { 'action': 'add', 'user': user.item_id, 'comment': comment, 'notify': notify, 'sticky': sticky } if item.item_type == 'service': data['host'] = item.data['host'] data['service'] = item.item_id else: data['host'] = item.item_id data['service'] = None request = self.post('actionacknowledge', data) return request # pylint: disable=too-many-arguments def downtime(self, item, fixed, duration, start_stamp, end_stamp, comment): # pragma: no cover """ Prepare data for downtime and POST on backend API or WS if available :param item: item to downtime: host | service :type item: alignak_app.items.host.Host | alignak_app.items.service.Service :param fixed: define if donwtime is fixed or not :type fixed: bool :param duration: duration timestamp of downtime :type duration: int :param start_stamp: start timestamp of downtime :type start_stamp: int :param end_stamp: end timestamp of downtime :type end_stamp: int :param comment: comment of downtime :type comment: str :return: request response :rtype: dict """ if self.ws_client.auth: if item.item_type == 'service': host = data_manager.get_item('host', '_id', item.data['host']) element = host.name else: element = item.name fixed = str(int(fixed)) item_name = item.name trigger_id = '0' parameters = ';'.join([ item_name, str(start_stamp), str(end_stamp), fixed, trigger_id, str(duration), data_manager.database['user'].name, comment ]) data = { 'command': 'SCHEDULE_SVC_DOWNTIME' if item.item_type == 'service' else 'SCHEDULE_HOST_DOWNTIME', 'element': element, 'parameters': parameters } request = self.ws_client.post('command', params=data) else: data = { 'action': 'add', 'user': data_manager.database['user'].item_id, 'fixed': fixed, 'duration': duration, 'start_time': start_stamp, 'end_time': end_stamp, 'comment': comment, } if item.item_type == 'service': data['host'] = item.data['host'] data['service'] = item.item_id else: data['host'] = item.item_id data['service'] = None request = app_backend.post('actiondowntime', data) return request def query_realms(self): """ Launch a request on ``realm`` endpoint """ request_model = Realm.get_request_model() request = self.get(request_model['endpoint'], request_model['params'], request_model['projection']) if request: realms_list = [] for backend_item in request['_items']: realm = Realm() realm.create( backend_item['_id'], backend_item, backend_item['name'], ) realms_list.append(realm) if realms_list: data_manager.update_database('realm', realms_list) if 'OK' in request['_status']: data_manager.db_is_ready[request_model['endpoint']] = True def query_timeperiods(self): """ Launch a request on ``timeperiod`` endpoint """ request_model = Period.get_request_model() request = self.get(request_model['endpoint'], request_model['params'], request_model['projection']) if request: periods_list = [] for backend_item in request['_items']: period = Period() period.create( backend_item['_id'], backend_item, backend_item['name'], ) periods_list.append(period) if periods_list: data_manager.update_database('timeperiod', periods_list) if 'OK' in request['_status']: data_manager.db_is_ready[request_model['endpoint']] = True def query_user(self): """ Launch request on "user" endpoint. Only for current App user. """ request_model = User.get_request_model(self.backend.token) request = self.get(request_model['endpoint'], request_model['params'], request_model['projection']) if request: if request['_items']: user = User() user.create(request['_items'][0]['_id'], request['_items'][0], request['_items'][0]['name']) data_manager.update_database('user', user) if 'OK' in request['_status']: data_manager.db_is_ready[request_model['endpoint']] = True def query_hosts(self): """ Launch request on "host" endpoint, add hosts in problems if needed """ request_model = Host.get_request_model() request = self.get(request_model['endpoint'], request_model['params'], request_model['projection'], all_items=True) if request: hosts_list = [] for backend_item in request['_items']: host = Host() host.create( backend_item['_id'], backend_item, backend_item['name'], ) hosts_list.append(host) # If host is a problem, add / update it if data_manager.is_problem('host', backend_item): if data_manager.get_item('problems', host.item_id): data_manager.update_item_data('problems', host.item_id, host.data) else: data_manager.database['problems'].append(host) data_manager.db_is_ready['problems']['host'] = True if hosts_list: data_manager.update_database('host', hosts_list) if 'OK' in request['_status']: data_manager.db_is_ready[request_model['endpoint']] = True def query_services(self, host_id=None): """ Launch request for "service" endpoint. If ``host_id`` is given, only services related to host are added / updated :param host_id: "_id" of host :type host_id: str """ request_model = Service.get_request_model(host_id) request = self.get(request_model['endpoint'], request_model['params'], request_model['projection'], all_items=True) if request: services_list = [] for backend_item in request['_items']: service = Service() service.create( backend_item['_id'], backend_item, backend_item['name'], ) # Add / update only services of host "if host_id" if host_id: if not data_manager.get_item('service', service.item_id): logger.debug('Add item data in database[service]') data_manager.database['service'].append(service) else: data_manager.update_item_data('service', service.item_id, service.data) # If not item ID, update all database if services_list and not host_id: data_manager.update_database('service', services_list) if host_id: host = data_manager.get_item('host', '_id', host_id) if host: logger.info('Update database[service] for %s', host.name) if 'OK' in request['_status']: data_manager.db_is_ready[request_model['endpoint']] = True def query_services_problems(self, state): """ Launch requests on "service" endpoint to get items with "ls_state = state" Wanted states are: ``WARNING``, ``CRITICAL`` and ``UNKNOWN`` :param state: state of service :type state: str """ # Services services_projection = [ 'name', 'host', 'alias', 'ls_state', 'ls_output', 'ls_acknowledged', 'ls_downtimed', 'passive_checks_enabled', 'active_checks_enabled' ] params = { 'where': json.dumps({ '_is_template': False, 'ls_state': state }) } request = self.get('service', params, services_projection, all_items=True) if request: for backend_item in request['_items']: if data_manager.is_problem('service', backend_item): service = Service() service.create(backend_item['_id'], backend_item, backend_item['name']) if data_manager.get_item('problems', service.item_id): data_manager.update_item_data('problems', service.item_id, service.data) else: data_manager.database['problems'].append(service) # Problems state is ready data_manager.db_is_ready['problems'][state] = True logger.info("Update database[problems] for %s services...", state) def query_alignakdaemons(self): """ Launch request on "alignakdaemon" endpoint """ request_model = Daemon.get_request_model() request = self.get(request_model['endpoint'], request_model['params'], request_model['projection'], all_items=True) if request: daemons_list = [] for backend_item in request['_items']: daemon = Daemon() daemon.create( backend_item['_id'], backend_item, backend_item['name'], ) daemons_list.append(daemon) if daemons_list: data_manager.update_database('alignakdaemon', daemons_list) if 'OK' in request['_status']: data_manager.db_is_ready[request_model['endpoint']] = True def query_livesynthesis(self): """ Launch request on "livesynthesis" endpoint """ request_model = LiveSynthesis.get_request_model() request = self.get(request_model['endpoint'], request_model['params'], request_model['projection'], all_items=True) if request: livesynthesis = [] for backend_item in request['_items']: synthesis = LiveSynthesis() synthesis.create( backend_item['_id'], backend_item, ) livesynthesis.append(synthesis) if livesynthesis: data_manager.update_database('livesynthesis', livesynthesis) if 'OK' in request['_status']: data_manager.db_is_ready[request_model['endpoint']] = True def query_history(self, hostname=None, host_id=None): """ Launch request on "history" endpoint but only for hosts in "data_manager" :param hostname: name of host we want history :type hostname: str :param host_id: id of host for history :type host_id: str """ request_model = History.get_request_model() if hostname and host_id: request_model['params']['where'] = json.dumps({'host': host_id}) request_model['params']['max_results'] = 25 request = self.get(request_model['endpoint'], request_model['params'], request_model['projection'], all_items=False) if request: logger.debug('Add / Update history for %s (%s)', hostname, host_id) if data_manager.get_item('history', host_id): data_manager.update_item_data('history', host_id, request['_items']) else: host_history = History() host_history.create( host_id, request['_items'], hostname, ) data_manager.database['history'].append(host_history) else: # pragma: no cover, too long to test history_list = [] for history in data_manager.database['history']: request_model['params']['where'] = json.dumps( {'host': history.item_id}) request_model['params']['max_results'] = 25 request = self.get(request_model['endpoint'], request_model['params'], request_model['projection'], all_items=False) if request: host_history = History() host_history.create( history.item_id, request['_items'], history.name, ) history_list.append(host_history) if history_list: data_manager.update_database('history', history_list) def query_notifications( self): # pragma: no cover, notifications can be empty """ Launch request on "history" endpoint. Only for 'type': 'monitoring.notification' and for current App user """ request_model = Event.get_request_model() request = self.get(request_model['endpoint'], request_model['params'], request_model['projection'], all_items=False) if request: notifications = [] for backend_item in request['_items']: message_split = backend_item['message'].split(';') user = message_split[0].split(':')[1].strip() if 'imported_admin' in user: user = '******' if user == data_manager.database['user'].name: notification = Event() notification.create( backend_item['_id'], backend_item, ) notifications.append(notification) if notifications: data_manager.update_database('notifications', notifications) def get_backend_status_icon(self): """ Return backend status icon name :return: status icon name :rtype: str """ if self.connected: return Daemon.get_states('ok') return Daemon.get_states('ko') def get_ws_status_icon(self): """ Return Web Service status icon name :return: status icon name :rtype: str """ if self.ws_client.auth: return Daemon.get_states('ok') return Daemon.get_states('ko')
def test_04_login(self): """ Test with right username / password :return: None """ print('') print('test accepted connection with username/password') # Create client API backend = Backend(self.backend_address) print('Login ...') assert backend.login('admin', 'admin') print('authenticated:', backend.authenticated) print('token:', backend.token) assert_true(backend.authenticated) print('Logout ...') backend.logout() print('authenticated:', backend.authenticated) print('token:', backend.token) assert_false(backend.authenticated) print('Login ...') print('authenticated:', backend.authenticated) assert backend.login('admin', 'admin') print('authenticated:', backend.authenticated) print('token:', backend.token) assert_true(backend.authenticated) print('Logout ...') backend.logout() print('authenticated:', backend.authenticated) print('token:', backend.token) assert_false(backend.authenticated) print('Logout ...') backend.logout() print('authenticated:', backend.authenticated) print('token:', backend.token) assert_false(backend.authenticated) print('get object ... must be refused!') with assert_raises(BackendException) as cm: backend.get('host') ex = cm.exception print('exception:', str(ex.code)) assert_true(ex.code == 401, str(ex)) print('get_all object ... must be refused!') with assert_raises(BackendException) as cm: backend.get_all('host') ex = cm.exception print('exception:', str(ex.code)) assert_true(ex.code == 401, str(ex)) print('get all domains ... must be refused!') with assert_raises(BackendException) as cm: backend.get_domains() ex = cm.exception print('exception:', str(ex.code)) assert_true(ex.code == 401, str(ex)) print('post data ... must be refused!') with assert_raises(BackendException) as cm: data = {'fake': 'fake'} backend.post('user', data=data) ex = cm.exception print('exception:', str(ex.code)) assert_true(ex.code == 401, str(ex)) print('patch data ... must be refused!') with assert_raises(BackendException) as cm: data = {'fake': 'fake'} headers = {'If-Match': ''} backend.patch('user', data=data, headers=headers) ex = cm.exception print('exception:', str(ex.code)) assert_true(ex.code == 405, str(ex)) print('delete data ... must be refused!') with assert_raises(BackendException) as cm: headers = {'If-Match': ''} backend.delete('user', headers=headers) ex = cm.exception print('exception:', str(ex.code)) assert_true(ex.code == 401, str(ex))
class AlignakBackendScheduler(BaseModule): """ This class is used to send live states to alignak-backend """ def __init__(self, mod_conf): """ Module initialization mod_conf is a dictionary that contains: - all the variables declared in the module configuration file - a 'properties' value that is the module properties as defined globally in this file :param mod_conf: module configuration file as a dictionary """ BaseModule.__init__(self, mod_conf) # pylint: disable=global-statement global logger logger = logging.getLogger("alignak.module.%s" % self.alias) logger.debug("inner properties: %s", self.__dict__) logger.debug("received configuration: %s", mod_conf.__dict__) self.url = getattr(mod_conf, "api_url", "http://localhost:5000") self.backend = Backend(self.url) self.backend.token = getattr(mod_conf, "token", "") self.backend_connected = False if self.backend.token == "": self.getToken( getattr(mod_conf, "username", ""), getattr(mod_conf, "password", ""), getattr(mod_conf, "allowgeneratetoken", False), ) # Common functions def do_loop_turn(self): """This function is called/used when you need a module with a loop function (and use the parameter 'external': True) """ logger.info("[Backend Scheduler] In loop") time.sleep(1) def getToken(self, username, password, generatetoken): """ Authenticate and get the token :param username: login name :type username: str :param password: password :type password: str :param generatetoken: if True allow generate token, otherwise not generate :type generatetoken: bool :return: None """ generate = "enabled" if not generatetoken: generate = "disabled" try: self.backend.login(username, password, generate) self.backend_connected = True except BackendException as exp: logger.warning("Alignak backend is not available for login. " "No backend connection.") logger.exception("Exception: %s", exp) self.backend_connected = False def hook_load_retention(self, scheduler): """ Load retention data from alignak-backend :param scheduler: scheduler instance of alignak :type scheduler: object :return: None """ all_data = {"hosts": {}, "services": {}} if not self.backend_connected: logger.error( "[Backend Scheduler] Alignak backend connection is not available. " "Skipping objects retention load." ) else: # Get data from the backend response = self.backend.get_all("retentionhost") for host in response["_items"]: # clean unusable keys hostname = host["host"] for key in ["_created", "_etag", "_id", "_links", "_updated", "host"]: del host[key] all_data["hosts"][hostname] = host response = self.backend.get_all("retentionservice") for service in response["_items"]: # clean unusable keys servicename = (service["service"][0], service["service"][1]) for key in ["_created", "_etag", "_id", "_links", "_updated", "service"]: del service[key] all_data["services"][servicename] = service scheduler.restore_retention_data(all_data) def hook_save_retention(self, scheduler): """ Save retention data from alignak-backend :param scheduler: scheduler instance of alignak :type scheduler: object :return: None """ data_to_save = scheduler.get_retention_data() if not self.backend_connected: logger.error("Alignak backend connection is not available. " "Skipping objects retention save.") return # clean hosts we will re-upload the retention response = self.backend.get_all("retentionhost") for host in response["_items"]: if host["host"] in data_to_save["hosts"]: delheaders = {"If-Match": host["_etag"]} self.backend.delete("/".join(["retentionhost", host["_id"]]), headers=delheaders) # Add all hosts after for host in data_to_save["hosts"]: data_to_save["hosts"][host]["host"] = host try: self.backend.post("retentionhost", data=data_to_save["hosts"][host]) except BackendException as exp: logger.error("Post retentionhost for host error") logger.error("Response: %s", exp.response) logger.exception("Exception: %s", exp) return logger.info("%d hosts saved in retention", len(data_to_save["hosts"])) # clean services we will re-upload the retention response = self.backend.get_all("retentionservice") for service in response["_items"]: if (service["service"][0], service["service"][1]) in data_to_save["services"]: delheaders = {"If-Match": service["_etag"]} self.backend.delete("/".join(["retentionservice", service["_id"]]), headers=delheaders) # Add all services after for service in data_to_save["services"]: data_to_save["services"][service]["service"] = service try: self.backend.post("retentionservice", data=data_to_save["services"][service]) except BackendException as exp: logger.error("Post retentionservice for service error") logger.exception("Exception: %s", exp) return logger.info("%d services saved in retention", len(data_to_save["services"]))