blob: 319488de6349e6895daa0b5507f1a318fad316b7 [file] [log] [blame]
Mateusz Matuszkowiak2820c662018-11-21 12:07:25 +01001# Copyright 2018: Mirantis Inc.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16import hashlib
17import logging
18import os
19import uuid
20
21import requests
22
23from simple_salesforce import Salesforce
24from simple_salesforce import exceptions as sf_exceptions
25
26
27STATE_MAP = {
28 'OK': '060 Informational',
29 'UP': '060 Informational',
30 'UNKNOWN': '070 Unknown',
31 'WARNING': '080 Warning',
32 'MINOR': '080 Warning',
33 'MAJOR': '090 Critical',
34 'CRITICAL': '090 Critical',
35 'DOWN': '090 Critical',
36 'UNREACHABLE': '090 Critical',
37}
38
39CONFIG_FIELD_MAP = {
40 'auth_url': 'instance',
41 'username': 'username',
42 'password': 'password',
43 'organization_id': 'organizationId',
44 'environment_id': 'environment_id',
45 'sandbox_enabled': 'domain',
46}
47
48logger = logging.getLogger(__name__)
49
50
51def sf_auth_retry(method):
52 def wrapper(self, *args, **kwargs):
53 try:
54 return method(self, *args, **kwargs)
55 except sf_exceptions.SalesforceExpiredSession:
56 logger.warning('Salesforce session expired.')
57 self._auth()
58 return method(self, *args, **kwargs)
59 return wrapper
60
61
62class SfNotifierError(Exception):
Michal Kobusee36c422018-11-26 15:02:31 +010063 pass
Mateusz Matuszkowiak2820c662018-11-21 12:07:25 +010064
65
66class SalesforceClient(object):
67
68 def __init__(self, config):
69 self.session = requests.Session()
70 self.config = self._validate_config(config)
71 self.environment = self.config.pop('environment_id')
72 self._auth()
73 self._registered_alerts = {}
74
Michal Kobusee36c422018-11-26 15:02:31 +010075 @staticmethod
76 def _validate_config(config):
77 kwargs = {}
Mateusz Matuszkowiak2820c662018-11-21 12:07:25 +010078
79 for param, field in CONFIG_FIELD_MAP.iteritems():
80 setting_var = param.upper()
81 env_var = 'SFDC_{}'.format(setting_var)
82 kwargs[field] = os.environ.get(
Michal Kobusee36c422018-11-26 15:02:31 +010083 env_var, config.get(setting_var))
Mateusz Matuszkowiak2820c662018-11-21 12:07:25 +010084
85 if field == 'domain':
Michal Kobus17726ae2018-11-27 12:59:55 +010086 if kwargs[field] in ['true', 'True', True]:
Michal Kobusee36c422018-11-26 15:02:31 +010087 kwargs[field] = 'test'
88 else:
89 del kwargs[field]
Mateusz Matuszkowiak2820c662018-11-21 12:07:25 +010090 continue
91
92 if kwargs[field] is None:
93 msg = ('Invalid config: missing "{}" field or "{}" environment'
94 ' variable.').format(param, env_var)
95 logger.error(msg)
96 raise SfNotifierError(msg)
97 return kwargs
98
99 def _auth(self):
Michal Kobusee36c422018-11-26 15:02:31 +0100100 kwargs = {'session': self.session}
101 kwargs.update(self.config)
Michal Kobus17726ae2018-11-27 12:59:55 +0100102 try:
103 self.sf = Salesforce(**kwargs)
104 except sf_exceptions.SalesforceAuthenticationFailed:
105 logger.error('Salesforce authentication failure.')
106 return
Mateusz Matuszkowiak2820c662018-11-21 12:07:25 +0100107 logger.info('Salesforce authentication successful.')
108
Michal Kobusee36c422018-11-26 15:02:31 +0100109 @staticmethod
110 def _get_alert_id(labels):
Mateusz Matuszkowiak2820c662018-11-21 12:07:25 +0100111 alert_id_data = ''
112 for key in sorted(labels):
113 alert_id_data += labels[key].replace(".", "\\.")
114 return hashlib.sha256(alert_id_data).hexdigest()
115
116 @sf_auth_retry
117 def _create_case(self, subject, body, labels, alert_id):
118
119 if alert_id in self._registered_alerts:
120 logger.info('Duplicate case for alert: {}.'.format(alert_id))
121 return 1, self._registered_alerts[alert_id]['Id']
122
123 severity = labels.get('severity', 'unknown').upper()
124 payload = {
125 'Subject': subject,
126 'Description': body,
127 'IsMosAlert__c': 'true',
128 'Alert_Priority__c': STATE_MAP.get(severity, '070 Unknown'),
129 'Alert_Host__c': labels.get('host') or labels.get(
130 'instance', 'UNKNOWN'
131 ),
132 'Alert_Service__c': labels.get('service', 'UNKNOWN'),
133 'Environment2__c': self.environment,
134 'Alert_ID__c': alert_id,
135 }
136 logger.info('Try to create case: {}'.format(payload))
137 try:
138 case = self.sf.Case.create(payload)
139 except sf_exceptions.SalesforceMalformedRequest as ex:
140 msg = ex.content[0]['message']
141 err_code = ex.content[0]['errorCode']
142
143 if err_code == 'DUPLICATE_VALUE':
Michal Kobus17726ae2018-11-27 12:59:55 +0100144 logger.warning('Duplicate case: {}.'.format(msg))
Mateusz Matuszkowiak2820c662018-11-21 12:07:25 +0100145 case_id = msg.split()[-1]
146 self._registered_alerts[alert_id] = {'Id': case_id}
147 return 1, case_id
148 else:
149 raise
150
151 self._registered_alerts[alert_id] = {'Id': case['id']}
152 return 0, case['id']
153
154 @sf_auth_retry
155 def _get_case(self, case_id):
156 return self.sf.Case.get(case_id)
157
158 @sf_auth_retry
159 def _update_case(self, case_id, data):
160 return self.sf.Case.update(case_id, data)
161
162 @sf_auth_retry
163 def _close_case(self, case_id):
164 logger.info('Try to close case: {}.'.format(case_id))
165 update = self.sf.Case.update(
166 case_id,
167 {'Status': 'Auto-solved', 'Alert_ID__c': uuid.uuid4().hex}
168 )
169 logger.info('Closed case: {}.'.format(case_id))
170 return update
171
172 @sf_auth_retry
173 def _create_feed_item(self, subject, body, case_id):
174 feed_item = {'Title': subject, 'ParentId': case_id, 'Body': body}
175 return self.sf.FeedItem.create(feed_item)
176
177 @sf_auth_retry
178 def _get_case_by_alert_id(self, alert_id):
179 logger.info('Try to get case by alert ID: {}.'.format(alert_id))
180
181 if alert_id in self._registered_alerts:
182 return self._registered_alerts[alert_id]
183 try:
184 return self.sf.Case.get_by_custom_id('Alert_ID__c', alert_id)
185 except sf_exceptions.SalesforceResourceNotFound:
186 logger.warning('Alert ID: {} was already solved.'.format(alert_id))
187
188 def create_case(self, subject, body, status, labels):
189 alert_id = self._get_alert_id(labels)
190
191 error_code, case_id = self._create_case(subject, body,
192 labels, alert_id)
193
194 response = {'case_id': case_id, 'alert_id': alert_id,
195 'status': 'created'}
196
197 if error_code != 2:
198 self._create_feed_item(subject, body, case_id)
199 if error_code == 1:
200 response['status'] = 'duplicate'
201 return response
202
203 def close_case(self, labels):
204 alert_id = self._get_alert_id(labels)
205 case = self._get_case_by_alert_id(alert_id)
206
207 response = {'alert_id': alert_id, 'status': 'resolved'}
208
209 if case is None:
210 return response
211
212 if self._registered_alerts.get(alert_id):
213 del self._registered_alerts[alert_id]
214
215 response['case_id'] = case['Id']
216 response['closed'] = self._close_case(case['Id'])
217 return response