diff --git a/argusclient/client.py b/argusclient/client.py index 92e94a9..a981b8d 100644 --- a/argusclient/client.py +++ b/argusclient/client.py @@ -591,6 +591,85 @@ def get_alerts_allinfo(self, ownerName=None, alertNameContains=None, shared=Fals """ return self.argus._request("get", "alerts/allinfo", params=dict(ownername=ownerName, alertNameContains=alertNameContains, shared=shared, limit=limit)) + ''' + Functions to enable support for composite alerts + ''' + + def get_composite_alert_children(self, comp_alert_id): + """ + Get list of child alerts for a composite alert + :param comp_alert_id: ID of an argus composite alert + :type comp_alert_id: integer + :return: list of :class:`argusclient.model.Alert` object with all fields populated. + """ + + if not comp_alert_id: raise ValueError("Need to specify comp_alert_id") + if not isinstance(comp_alert_id, int): raise TypeError("Need an Alert ID, got: %s" % type(comp_alert_id)) + + uri_path = "alerts/{}/children".format(comp_alert_id) + child_alerts = self.argus._request("get", uri_path) + child_alerts = [self._fill(child_alert) for child_alert in child_alerts] + return child_alerts + + def get_composite_alert_children_info(self, comp_alert_id): + """ + Get information for all children (child alerts + triggers associated with them) of a composite alert + + :param comp_alert_id: ID of an argus composite alert + :type comp_alert_id: integer + :return: list of child alerts information (alertid, alertname, triggerids, triggernames etc) + """ + + if not comp_alert_id: raise ValueError("Need to specify comp_alert_id") + if not isinstance(comp_alert_id, int): raise TypeError("Need an Alert ID, got: %s" % type(comp_alert_id)) + + uri_path = "alerts/{}/children/info".format(comp_alert_id) + return self.argus._request("get", uri_path) + + + def add_child_alert_to_composite_alert(self, comp_alert_id, alert): + """ + Add child alert to a composite alert + + :param comp_alert_id: ID of a composite alert + :type comp_alert_id: Integer + + :param alert: alert definition + :type alert: class:`argusclient.model.Alert` object + + :return: newly created child alert object of type class:`argusclient.model.Alert` + """ + if not comp_alert_id: raise ValueError("Need to specify a composite alert id") + if not alert: raise ValueError("Need to specify an Alert object") + if not isinstance(comp_alert_id, int): raise TypeError("Need an Alert ID, got: %s" % type(comp_alert_id)) + if not isinstance(alert, Alert): raise TypeError("Need an Alert object, got: %s" % type(alert)) + + uri_path = "alerts/{}/children".format(comp_alert_id) + alert_obj = self._fill(self.argus._request("post", uri_path, dataObj=alert)) + self._coll[alert_obj.id] = alert_obj + return alert_obj + + + def delete_child_alert_from_composite_alert(self, comp_alert_id, child_alert_id): + """ + Delete a child alert from a composite alert + + :param comp_alert_id: ID of a composite alert + :type comp_alert_id: Integer + + :param child_alert_id: ID of a child alert + :type child_alert_id: Integer + """ + if not comp_alert_id: raise ValueError("Need to specify a composite alert id") + if not child_alert_id: raise ValueError("Need to specify a child alert id") + if not isinstance(comp_alert_id, int): raise TypeError("Need a composite Alert ID, got: %s" % type(comp_alert_id)) + if not isinstance(child_alert_id, int): raise TypeError("Need an Alert ID, got: %s" % type(child_alert_id)) + + uri_path = "alerts/{}/children/{}".format(comp_alert_id, child_alert_id) + if child_alert_id in self._coll: + del self._coll[child_alert_id] + return self.argus._request("delete", uri_path) + class AlertTriggersServiceClient(BaseUpdatableModelServiceClient): """ diff --git a/docs/conf.py b/docs/conf.py index 2bfe80a..0d2f2e2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -48,9 +48,9 @@ # built documents. # # The short X.Y version. -version = '1.1' +version = '1.2' # The full version, including alpha/beta/rc tags. -release = '1.1.0' +release = '1.2.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index 08312d0..4091062 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ from setuptools import setup, find_packages -version = '1.1' +version = '1.2' with open("README.rst", 'r') as fin: README = fin.read() diff --git a/tests/test_data.py b/tests/test_data.py index ba0b3e7..eb67748 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -39,6 +39,12 @@ permissionNames = ["VIEW", "EDIT", "DELETE"] permissionGroupId = '24231-52321-43523-64353-23111' +compAlertID = 6000 +childAlertID_1 = 6003 +childAlertID_2 = 6009 +triggerID_1 = 6005 +compAlert_notificationID = 6007 + metric_D = { "scope": scope, "metric": metric, @@ -296,4 +302,58 @@ "triggers": [trigger_D, trigger_2_D], "notifications": [notification_D, notification_2_D, notification_3_D], "ownerName": userName +} + +compalert_D = { + 'id': compAlertID, + 'alertType': 'COMPOSITE', + 'name': 'CompAlertTest', + 'triggerIds': [], + 'enabled': False, + 'cronEntry': '*/2 * * * *', + 'notificationsIds': [], + 'expression': {'expression': {'operator': 'AND', 'join': [], 'terms': []}, 'termDefinitions': []}, + 'childAlertsIds': [] + } + +childAlert_1 = { + 'id': childAlertID_1, + 'triggersIds': [], + 'alertType': 'COMPOSITE_CHILD', + 'name': 'CompAlertTest-ChildAlert-1', + 'cronEntry': '* * * * *', + 'notificationsIds': [], + 'enabled': False, + 'expression': '-1h:-0m:tracer.api.XRD.NONE.none:requestsReceivedLastMin:avg:1m-avg' +} + +childAlert_2 = { + 'id': childAlertID_2, + 'triggersIds': [], + 'alertType': 'COMPOSITE_CHILD', + 'name': 'CompAlertTest-ChildAlert-2', + 'cronEntry': '* * * * *', + 'notificationsIds': [], + 'enabled': False, + 'expression': '-1h:-0m:tracer.api.XRD.NONE.none:avgQueryTime:avg:1m-avg' +} + +childAlert_trigger_1 = { + 'id': triggerID_1, + 'threshold': 1.0, + 'type': 'GREATER_THAN', + 'inertia': 0L, + 'name': 'CompAlertTest/trigger1' +} + +compAlert_notification = { + 'id': compAlert_notificationID, + 'severityLevel': 5, + 'name': 'Email1', + 'subscriptions': ['jdoe@example.com'], + 'notifierName': 'com.salesforce.dva.argus.service.alert.notifier.EmailNotifier', + 'metricsToAnnotate': [], + 'cooldownPeriod': 0L, + 'sractionable': False, + 'customText': 'None' } \ No newline at end of file diff --git a/tests/test_service.py b/tests/test_service.py index 2220b17..8e15769 100644 --- a/tests/test_service.py +++ b/tests/test_service.py @@ -830,3 +830,171 @@ def testGetAlertWithMultipleTriggers(self): self.assertEquals(len(alert.triggers), 2) self.assertEquals(alert.triggers[100].argus_id, 100) self.assertEquals(alert.triggers[101].argus_id, 101) + + + +class TestCompositeAlert(TestServiceBase): + + def _createCompAlert(self): + with mock.patch('requests.Session.post', return_value=MockResponse(json.dumps(compalert_D), 200)) as mock_add_comp_alert: + alert = Alert.from_dict(compalert_D) + self.assertTrue(isinstance(alert, Alert)) + delattr(alert, "id") + comp_alert = self.argus.alerts.add(alert) + self.assertTrue(isinstance(comp_alert, Alert)) + self.assertTrue(hasattr(comp_alert, "id")) + self.assertEqual(comp_alert.expression['expression']['operator'], 'AND') + call_args = mock_add_comp_alert.call_args + uri_path = os.path.join(endpoint, "alerts") + self.assertIn((uri_path,), call_args) + return comp_alert + + def testAddCompAlert(self): + self._createCompAlert() + + + def testAddChildAlert(self): + comp_alert = self._createCompAlert() + + with mock.patch('requests.Session.post', return_value=MockResponse(json.dumps(childAlert_1), 200)): + child_alert = self.argus.alerts.add_child_alert_to_composite_alert(comp_alert.id, Alert.from_dict(childAlert_1)) + self.assertEqual(child_alert.alertType, 'COMPOSITE_CHILD') + self.assertTrue(isinstance(child_alert, Alert)) + + def testAddTriggerToChildAlert(self): + comp_alert = self._createCompAlert() + + with mock.patch('requests.Session.post', return_value=MockResponse(json.dumps(childAlert_1), 200)) as mock_add_childalert: + child_alert = self.argus.alerts.add_child_alert_to_composite_alert(comp_alert.id, + Alert.from_dict(childAlert_1)) + self.assertEqual(child_alert.alertType, 'COMPOSITE_CHILD') + self.assertTrue(isinstance(child_alert, Alert)) + call_args = tuple(mock_add_childalert.call_args) + uri_path = os.path.join(endpoint, "alerts/{}/children".format(comp_alert.id)) + self.assertIn((uri_path,), call_args) + + with mock.patch('requests.Session.post', return_value=MockResponse(json.dumps([childAlert_trigger_1]), 200)) as mock_trigger_post: + trigger_obj = Trigger.from_dict(childAlert_trigger_1) + delattr(trigger_obj, "id") + trigger = child_alert.triggers.add(trigger_obj) + self.assertTrue(isinstance(trigger, Trigger)) + call_args = tuple(mock_trigger_post.call_args) + uri_path = os.path.join(endpoint, "alerts/{}/triggers".format(child_alert.id)) + self.assertIn((uri_path,), call_args) + + def testAddNotification(self): + comp_alert = self._createCompAlert() + + with mock.patch('requests.Session.post', return_value=MockResponse(json.dumps([compAlert_notification]), 200)) as mock_notification: + notification_obj = Notification.from_dict(compAlert_notification) + delattr(notification_obj, "id") + notification = comp_alert.notifications.add(notification_obj) + self.assertTrue(isinstance(notification, Notification)) + call_args = mock_notification.call_args + uri_path = os.path.join(endpoint, "alerts/{}/notifications".format(comp_alert.id)) + self.assertIn((uri_path,), call_args) + + + def testDeleteChildAlert(self): + comp_alert = self._createCompAlert() + with mock.patch('requests.Session.post', return_value=MockResponse(json.dumps(childAlert_1), 200)): + child_alert = self.argus.alerts.add_child_alert_to_composite_alert(comp_alert.id, Alert.from_dict(childAlert_1)) + self.assertEqual(child_alert.alertType, 'COMPOSITE_CHILD') + self.assertTrue(isinstance(child_alert, Alert)) + + ''' + Right after add, we can access the child_alert.id without triggering an API call (i.e., no mocking is required) + as it gets added to the local cache + ''' + res = self.argus.alerts.get(child_alert.id) + with mock.patch('requests.Session.delete', return_value=MockResponse("", 200)) as mock_delete: + self.argus.alerts.delete_child_alert_from_composite_alert(comp_alert.id, child_alert.id) + call_args = mock_delete.call_args + uri_path = os.path.join(endpoint, "alerts/{}/children/{}".format(comp_alert.id, child_alert.id)) + self.assertIn((uri_path,), call_args) + + ''' + After delete, the object should be gone from the local cache, so the get should result in an API call which + we are mocking to raise a 404 to mimic the real scenario + ''' + with mock.patch('requests.Session.get', return_value=MockResponse("", 404)) as mockGet: + self.failUnlessRaises(ArgusObjectNotFoundException, lambda: self.argus.alerts.get(child_alert.id)) + + def testDeleteTriggerFromChildAlert(self): + comp_alert = self._createCompAlert() + + with mock.patch('requests.Session.post', return_value=MockResponse(json.dumps(childAlert_1), 200)) as mock_add_childalert: + child_alert = self.argus.alerts.add_child_alert_to_composite_alert(comp_alert.id, + Alert.from_dict(childAlert_1)) + self.assertEqual(child_alert.alertType, 'COMPOSITE_CHILD') + self.assertTrue(isinstance(child_alert, Alert)) + call_args = tuple(mock_add_childalert.call_args) + uri_path = os.path.join(endpoint, "alerts/{}/children".format(comp_alert.id)) + self.assertIn((uri_path,), call_args) + + with mock.patch('requests.Session.post', return_value=MockResponse(json.dumps([childAlert_trigger_1]), 200)) as mock_trigger_post: + trigger_obj = Trigger.from_dict(childAlert_trigger_1) + delattr(trigger_obj,"id") + trigger = child_alert.triggers.add(trigger_obj) + self.assertTrue(isinstance(trigger, Trigger)) + call_args = tuple(mock_trigger_post.call_args) + uri_path = os.path.join(endpoint, "alerts/{}/triggers".format(child_alert.id)) + self.assertIn((uri_path,), call_args) + + with mock.patch('requests.Session.delete', return_value=MockResponse("", 200)) as mock_delete: + child_alert.triggers.delete(trigger.id) + call_args = tuple(mock_trigger_post.call_args) + uri_path = os.path.join(endpoint, "alerts/{}/triggers".format(child_alert.id)) + self.assertIn((uri_path,), call_args) + + def testDeleteNotification(self): + comp_alert = self._createCompAlert() + + with mock.patch('requests.Session.post', return_value=MockResponse(json.dumps([compAlert_notification]), 200)) as mock_notification: + notification_obj = Notification.from_dict(compAlert_notification) + delattr(notification_obj, "id") + notification = comp_alert.notifications.add(notification_obj) + self.assertTrue(isinstance(notification, Notification)) + call_args = mock_notification.call_args + uri_path = os.path.join(endpoint, "alerts/{}/notifications".format(comp_alert.id)) + self.assertIn((uri_path,), call_args) + + with mock.patch('requests.Session.delete', return_value=MockResponse("", 200)) as mock_delete: + comp_alert.notifications.delete(notification.id) + call_args = tuple(mock_delete.call_args) + uri_path = os.path.join(endpoint, "alerts/{}/notifications/{}".format(comp_alert.id, notification.id)) + self.assertIn((uri_path,), call_args) + + def testGetCompAlertChildrenInfo(self): + with mock.patch('requests.Session.get', return_value=MockResponse(json.dumps([childAlert_1, childAlert_2]), 200)) as mock_get: + res = self.argus.alerts.get_composite_alert_children_info(compAlertID) + if res: + for obj in res: + self.assertTrue(isinstance(obj, Alert)) + call_args = tuple(mock_get.call_args) + uri_path = os.path.join(endpoint, "alerts/{}/children/info".format(compAlertID)) + self.assertIn((uri_path,), call_args) + + def testGetCompAlertChildren(self): + with mock.patch('requests.Session.get', return_value=MockResponse(json.dumps([childAlert_1, childAlert_2]), 200)) as mock_get: + res = self.argus.alerts.get_composite_alert_children(compAlertID) + if res: + for obj in res: + self.assertTrue(isinstance(obj, Alert)) + call_args = tuple(mock_get.call_args) + uri_path = os.path.join(endpoint, "alerts/{}/children".format(compAlertID)) + self.assertIn((uri_path,), call_args) + + def testUpdateCompAlert(self): + comp_alert = self._createCompAlert() + + with mock.patch('requests.Session.put', return_value=MockResponse(json.dumps(compalert_D), 200)) as mock_update: + self.argus.alerts.update(compAlertID, Alert.from_dict(compalert_D)) + alert_obj = self.argus.alerts.get(compAlertID) + self.assertTrue(isinstance(alert_obj, Alert)) + alert_obj_dict = alert_obj.to_dict() + alert_dict = compalert_D + self.assertEquals(alert_obj_dict, alert_dict) + call_args = mock_update.call_args + uri_path = os.path.join(endpoint, "alerts/{}".format(compAlertID)) + self.assertIn((uri_path,), call_args) \ No newline at end of file