Revert commit fc1e7739506eb010707cf94ea02e412c8819c154
Revert adding common caching and offloading tasks
Change-Id: Ie07f8ba9f5f86f94b912d969ff8839d22e6eef88
Related-bug: PROD-30846
diff --git a/entrypoint.sh b/entrypoint.sh
index fbeb6ac..4e212e2 100755
--- a/entrypoint.sh
+++ b/entrypoint.sh
@@ -6,17 +6,11 @@
WORKERS=${SF_NOTIFIER_WORKERS:-4}
BUFFER=${SF_NOTIFIER_BUFFER_SIZE:-32768}
PORT=${SF_NOTIFIER_APP_PORT:-5000}
-CACHE_ITEMS=${SF_NOTIFIER_CACHE_ITEMS:-300}
-CACHE_FILE=${SF_NOTIFIER_CACHE_FILE:-/tmp/cachefile}
mkdir -p /var/log/sf-notifier
chown -R 999:999 /var/log/sf-notifier
-MULES=$(for _ in $(seq 1 $WORKERS); do
- echo "--mule "
-done)
-
-uwsgi -p 4 \
+uwsgi -p ${WORKERS} \
--uid 999 \
--gid 999 \
--http 0.0.0.0:${PORT} \
@@ -24,6 +18,4 @@
--callable app_dispatch \
--buffer-size=${BUFFER} \
--max-worker-lifetime 300 \
- --cache2 name=mycache,items=${CACHE_ITEMS},store=${CACHE_FILE},store_sync=10 \
- ${MULES} \
--master
diff --git a/sf_notifier/__init__.py b/sf_notifier/__init__.py
index 4e00609..e69de29 100644
--- a/sf_notifier/__init__.py
+++ b/sf_notifier/__init__.py
@@ -1,14 +0,0 @@
-# Copyright 2018: Mirantis Inc.
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
diff --git a/sf_notifier/helpers.py b/sf_notifier/helpers.py
index d2c713b..e9c221b 100644
--- a/sf_notifier/helpers.py
+++ b/sf_notifier/helpers.py
@@ -1,3 +1,18 @@
+# Copyright 2018: Mirantis Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
import os
diff --git a/sf_notifier/salesforce/__init__.py b/sf_notifier/salesforce/__init__.py
index 4e00609..e69de29 100644
--- a/sf_notifier/salesforce/__init__.py
+++ b/sf_notifier/salesforce/__init__.py
@@ -1,14 +0,0 @@
-# Copyright 2018: Mirantis Inc.
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
diff --git a/sf_notifier/salesforce/cache.py b/sf_notifier/salesforce/cache.py
deleted file mode 100644
index 25d76f4..0000000
--- a/sf_notifier/salesforce/cache.py
+++ /dev/null
@@ -1,63 +0,0 @@
-import pickle
-
-try:
- import uwsgi
-except ImportError:
- def uwsgi_lock(function):
- return function
-
- class Uwsgi(dict):
- def cache_get(self, *args, **kwargs):
- return self.get(*args, **kwargs)
-
- def cache_set(self, *args, **kwargs):
- return self.set(*args, **kwargs)
-
- def cache_update(self, *args, **kwargs):
- return self.update(*args, **kwargs)
-
- def cache_del(self, *args, **kwargs):
- return self.__delitem__(*args, **kwargs)
-
- def cache_exists(self, *args, **kwargs):
- return self.__contains__(*args, **kwargs)
-
- uwsgi = Uwsgi()
-
-
-class Cache(dict):
- def get(self, key):
- return self.__getitem__(key)
-
- def set(self, key, value):
- return self.__setitem__(key, value)
-
- def update(self, key, value):
- if not self.__contains__(key):
- return self.set(key, value)
-
- dump = pickle.dumps(value)
- return uwsgi.cache_update(key, dump)
-
- def delete(self, key):
- if self.__contains__(key):
- return self.__delitem__(key)
-
- def __getitem__(self, key):
- if not self.__contains__(key):
- return None
-
- dump = uwsgi.cache_get(key)
- return pickle.loads(dump)
-
- def __setitem__(self, key, value):
- dump = pickle.dumps(value)
- if self.__contains__(key):
- uwsgi.cache_update(key, dump)
- return uwsgi.cache_set(key, dump)
-
- def __delitem__(self, key):
- return uwsgi.cache_del(key)
-
- def __contains__(self, key):
- return uwsgi.cache_exists(key)
diff --git a/sf_notifier/salesforce/client.py b/sf_notifier/salesforce/client.py
index ee5db91..71ada18 100644
--- a/sf_notifier/salesforce/client.py
+++ b/sf_notifier/salesforce/client.py
@@ -1,26 +1,95 @@
+# Copyright 2018: Mirantis Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import fcntl
+import hashlib
import logging
+import os
import time
import uuid
+from contextlib import contextmanager
+
+from cachetools import TTLCache
from prometheus_client import Counter, Gauge
from requests import Session
+from requests.exceptions import ConnectionError as RequestsConnectionError
from simple_salesforce import Salesforce
-from simple_salesforce.exceptions import (SalesforceAuthenticationFailed,
- SalesforceMalformedRequest,
- SalesforceResourceNotFound)
+from simple_salesforce import exceptions as sf_exceptions
-from cache import Cache
-from decorators import flocked, sf_auth_retry
-from mixins import SalesforceMixin
-from settings import CASE_STATUS, SESSION_FILE, STATE_MAP
+STATE_MAP = {
+ 'OK': '060 Informational',
+ 'UP': '060 Informational',
+ 'UNKNOWN': '070 Unknown',
+ 'WARNING': '080 Warning',
+ 'MINOR': '080 Warning',
+ 'MAJOR': '090 Critical',
+ 'CRITICAL': '090 Critical',
+ 'DOWN': '090 Critical',
+ 'UNREACHABLE': '090 Critical',
+}
+
+CONFIG_FIELD_MAP = {
+ 'auth_url': 'instance_url',
+ 'username': 'username',
+ 'password': 'password',
+ 'organization_id': 'organizationId',
+ 'environment_id': 'environment_id',
+ 'sandbox_enabled': 'domain',
+}
+
+ALLOWED_HASHING = ('md5', 'sha256')
+SESSION_FILE = '/tmp/session'
logger = logging.getLogger(__name__)
-class SalesforceClient(SalesforceMixin):
+@contextmanager
+def flocked(fd):
+ try:
+ fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ yield
+ except IOError:
+ logger.info('Session file locked. Waiting 5 seconds...')
+ time.sleep(5)
+ finally:
+ fcntl.flock(fd, fcntl.LOCK_UN)
+
+
+def sf_auth_retry(method):
+ def wrapper(self, *args, **kwargs):
+ try:
+ return method(self, *args, **kwargs)
+ except sf_exceptions.SalesforceExpiredSession:
+ logger.warning('Salesforce session expired.')
+ self.auth()
+ except RequestsConnectionError:
+ logger.error('Salesforce connection error.')
+ self.auth()
+ return method(self, *args, **kwargs)
+ return wrapper
+
+
+class SfNotifierError(Exception):
+ pass
+
+
+class SalesforceClient(object):
def __init__(self, config):
self.metrics = {
@@ -31,16 +100,47 @@
self.config = self._validate_config(config)
self.hash_func = self._hash_func()
self.environment = self.config.pop('environment_id')
- self._registered_alerts = Cache()
+ self._registered_alerts = TTLCache(maxsize=2048, ttl=300)
self.sf = None
self.session = Session()
self.auth()
+ @staticmethod
+ def _hash_func():
+ name = os.environ.get('SF_NOTIFIER_ALERT_ID_HASH_FUNC', 'sha256')
+ if name in ALLOWED_HASHING:
+ return getattr(hashlib, name)
+ return hashlib.sha256
+
+ @staticmethod
+ def _validate_config(config):
+ kwargs = {}
+
+ for param, field in CONFIG_FIELD_MAP.iteritems():
+ setting_var = param.upper()
+ env_var = 'SFDC_{}'.format(setting_var)
+ kwargs[field] = os.environ.get(
+ env_var, config.get(setting_var))
+
+ if field == 'domain':
+ if kwargs[field] in ['true', 'True', True]:
+ kwargs[field] = 'test'
+ else:
+ del kwargs[field]
+ continue
+
+ if kwargs[field] is None:
+ msg = ('Invalid config: missing "{}" field or "{}" environment'
+ ' variable.').format(param, env_var)
+ logger.error(msg)
+ raise SfNotifierError(msg)
+ return kwargs
+
def _auth(self, config):
try:
config.update({'session': self.session})
self.sf = Salesforce(**config)
- except SalesforceAuthenticationFailed as ex:
+ except sf_exceptions.SalesforceAuthenticationFailed as ex:
logger.error('Salesforce authentication failure: {}.'.format(ex))
self.metrics['sf_auth_ok'].set(0)
return False
@@ -49,6 +149,13 @@
self.metrics['sf_auth_ok'].set(1)
return True
+ def _load_session(self, session_file):
+ lines = session_file.readlines()
+
+ if lines == []:
+ return
+ return lines[0]
+
def _refresh_ready(self, saved_session):
if saved_session is None:
logger.info('Current session is None.')
@@ -106,17 +213,18 @@
while auth_ok is False:
auth_ok = self._acquire_session()
+ def _get_alert_id(self, labels):
+ alert_id_data = ''
+ for key in sorted(labels):
+ alert_id_data += labels[key].replace(".", "\\.")
+ return self.hash_func(alert_id_data).hexdigest()
+
@sf_auth_retry
def _create_case(self, subject, body, labels, alert_id):
- cached_alert = self._registered_alerts.get(alert_id)
-
- if cached_alert is not None and cached_alert['id'] != 'error':
+ if alert_id in self._registered_alerts:
logger.warning('Duplicate case for alert: {}.'.format(alert_id))
- return cached_alert
-
- case = self._fmt_alert_update('pending', 1)
- self._registered_alerts.update(alert_id, case)
+ return 1, self._registered_alerts[alert_id]['Id']
severity = labels.get('severity', 'unknown').upper()
payload = {
@@ -136,26 +244,22 @@
self.metrics['sf_request_count'].inc()
case = self.sf.Case.create(payload)
logger.info('Created case: {}.'.format(case))
- except SalesforceMalformedRequest as ex:
+ except sf_exceptions.SalesforceMalformedRequest as ex:
msg = ex.content[0]['message']
err_code = ex.content[0]['errorCode']
if err_code == 'DUPLICATE_VALUE':
logger.warning('Duplicate case: {}.'.format(msg))
case_id = msg.split()[-1]
- case = self._fmt_alert_update(case_id, 1)
- self._registered_alerts.update(alert_id, case)
- return case
+ self._registered_alerts[alert_id] = {'Id': case_id}
+ return 1, case_id
- case = self._fmt_alert_update('error', 2, error_msg=msg)
- self._registered_alerts.update(alert_id, case)
logger.error('Cannot create case: {}.'.format(msg))
self.metrics['sf_error_count'].inc()
raise
- case = self._fmt_alert_update(case['id'], 0)
- self._registered_alerts.update(alert_id, case)
- return case
+ self._registered_alerts[alert_id] = {'Id': case['id']}
+ return 0, case['id']
@sf_auth_retry
def _close_case(self, case_id):
@@ -178,36 +282,33 @@
logger.info('Try to get case by alert ID: {}.'.format(alert_id))
if alert_id in self._registered_alerts:
- return self._registered_alerts.get(alert_id)
+ return self._registered_alerts[alert_id]
try:
return self.sf.Case.get_by_custom_id('Alert_ID__c', alert_id)
- except SalesforceResourceNotFound:
- self._registered_alerts.delete(alert_id)
+ except sf_exceptions.SalesforceResourceNotFound:
+ if self._registered_alerts.get(alert_id):
+ del self._registered_alerts[alert_id]
logger.warning('Alert ID: {} not found.'.format(alert_id))
def create_case(self, subject, body, labels):
- alert_id = self.get_alert_id(labels)
+ alert_id = self._get_alert_id(labels)
- case = self._create_case(subject, body, labels, alert_id)
+ error_code, case_id = self._create_case(subject, body,
+ labels, alert_id)
- if case['error_code'] == 0 or \
- (self._feed_update_ready(case['last_update']) and
- case['id'] not in CASE_STATUS):
- self._create_feed_item(subject, body, case['id'])
- case = self._fmt_alert_update(case['id'], case['error_code'])
- self._registered_alerts.update(alert_id, case)
+ self._create_feed_item(subject, body, case_id)
- response = {'case_id': case['id'], 'alert_id': alert_id}
+ response = {'case_id': case_id, 'alert_id': alert_id}
- if case['error_code'] == 1:
+ if error_code == 1:
response['status'] = 'duplicate'
else:
response['status'] = 'created'
return response
def close_case(self, labels):
- alert_id = self.get_alert_id(labels)
+ alert_id = self._get_alert_id(labels)
case = self._get_case_by_alert_id(alert_id)
response = {'alert_id': alert_id, 'status': 'resolved'}
@@ -215,8 +316,9 @@
if case is None:
return response
- self._registered_alerts.delete(alert_id)
+ if self._registered_alerts.get(alert_id):
+ del self._registered_alerts[alert_id]
- response['case_id'] = case['id']
- response['closed'] = self._close_case(case['id'])
+ response['case_id'] = case['Id']
+ response['closed'] = self._close_case(case['Id'])
return response
diff --git a/sf_notifier/salesforce/decorators.py b/sf_notifier/salesforce/decorators.py
deleted file mode 100644
index 82d845e..0000000
--- a/sf_notifier/salesforce/decorators.py
+++ /dev/null
@@ -1,36 +0,0 @@
-from contextlib import contextmanager
-import fcntl
-import logging
-import time
-
-from requests.exceptions import ConnectionError as RequestsConnectionError
-from simple_salesforce.exceptions import SalesforceExpiredSession
-
-
-logger = logging.getLogger(__name__)
-
-
-def sf_auth_retry(method):
- def wrapper(self, *args, **kwargs):
- try:
- return method(self, *args, **kwargs)
- except SalesforceExpiredSession:
- logger.warning('Salesforce session expired.')
- self.auth()
- except RequestsConnectionError:
- logger.error('Salesforce connection error.')
- self.auth()
- return method(self, *args, **kwargs)
- return wrapper
-
-
-@contextmanager
-def flocked(fd):
- try:
- fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
- yield
- except IOError:
- logger.info('Session file locked. Waiting 5 seconds...')
- time.sleep(5)
- finally:
- fcntl.flock(fd, fcntl.LOCK_UN)
diff --git a/sf_notifier/salesforce/exceptions.py b/sf_notifier/salesforce/exceptions.py
deleted file mode 100644
index 5ccd54e..0000000
--- a/sf_notifier/salesforce/exceptions.py
+++ /dev/null
@@ -1,2 +0,0 @@
-class SfNotifierError(Exception):
- pass
diff --git a/sf_notifier/salesforce/mixins.py b/sf_notifier/salesforce/mixins.py
deleted file mode 100644
index ce857cd..0000000
--- a/sf_notifier/salesforce/mixins.py
+++ /dev/null
@@ -1,78 +0,0 @@
-import datetime
-import hashlib
-import logging
-import os
-
-from exceptions import SfNotifierError
-from settings import (ALLOWED_HASHING, CONFIG_FIELD_MAP,
- FEED_UPDATE_INTERVAL)
-
-
-logger = logging.getLogger(__name__)
-
-
-class SalesforceMixin(object):
-
- @staticmethod
- def _hash_func():
- name = os.environ.get('SF_NOTIFIER_ALERT_ID_HASH_FUNC', 'sha256')
- if name in ALLOWED_HASHING:
- return getattr(hashlib, name)
- return hashlib.sha256
-
- @staticmethod
- def _feed_update_ready(update_time):
- now = datetime.datetime.now()
- feed_update_interval = datetime.timedelta(hours=FEED_UPDATE_INTERVAL)
- return now - update_time >= feed_update_interval
-
- @staticmethod
- def _validate_config(config):
- kwargs = {}
-
- for param, field in CONFIG_FIELD_MAP.iteritems():
- setting_var = param.upper()
- env_var = 'SFDC_{}'.format(setting_var)
- kwargs[field] = os.environ.get(
- env_var, config.get(setting_var))
-
- if field == 'domain':
- if kwargs[field] in ['true', 'True', True]:
- kwargs[field] = 'test'
- else:
- del kwargs[field]
- continue
-
- if kwargs[field] is None:
- msg = ('Invalid config: missing "{}" field or "{}" environment'
- ' variable.').format(param, env_var)
- logger.error(msg)
- raise SfNotifierError(msg)
- return kwargs
-
- @staticmethod
- def _load_session(session_file):
- lines = session_file.readlines()
-
- if lines == []:
- return
- return lines[0]
-
- @staticmethod
- def _fmt_alert_update(case_id, error_code, error_msg=None, now=None):
- now = now or datetime.datetime.now()
- row = {
- 'id': case_id,
- 'error_code': error_code,
- 'last_update': now
- }
- if error_msg is not None:
- row.update({'error': error_msg})
- return row
-
- def get_alert_id(self, labels, hash_func=None):
- hash_func = hash_func or self.hash_func
- alert_id_data = ''
- for key in sorted(labels):
- alert_id_data += labels[key].replace(".", "\\.")
- return hash_func(alert_id_data).hexdigest()
diff --git a/sf_notifier/salesforce/settings.py b/sf_notifier/salesforce/settings.py
deleted file mode 100644
index a4f3dd3..0000000
--- a/sf_notifier/salesforce/settings.py
+++ /dev/null
@@ -1,27 +0,0 @@
-STATE_MAP = {
- 'OK': '060 Informational',
- 'UP': '060 Informational',
- 'UNKNOWN': '070 Unknown',
- 'WARNING': '080 Warning',
- 'MINOR': '080 Warning',
- 'MAJOR': '090 Critical',
- 'CRITICAL': '090 Critical',
- 'DOWN': '090 Critical',
- 'UNREACHABLE': '090 Critical',
-}
-
-CONFIG_FIELD_MAP = {
- 'auth_url': 'instance_url',
- 'username': 'username',
- 'password': 'password',
- 'organization_id': 'organizationId',
- 'environment_id': 'environment_id',
- 'sandbox_enabled': 'domain',
-}
-
-ALLOWED_HASHING = ('md5', 'sha256')
-SESSION_FILE = '/tmp/session'
-# by default allow to send 2 feed items per hour
-FEED_ITEMS_THROTTLING = 2
-FEED_UPDATE_INTERVAL = 1.0 / FEED_ITEMS_THROTTLING
-CASE_STATUS = ('pending', 'error')
diff --git a/sf_notifier/server.py b/sf_notifier/server.py
index 9a5da5d..4d3ddc0 100644
--- a/sf_notifier/server.py
+++ b/sf_notifier/server.py
@@ -1,19 +1,35 @@
+# Copyright 2018: Mirantis Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
import json
from logging.config import dictConfig
-import time
from flask import Flask, Response, jsonify, request
-from prometheus_client import make_wsgi_app
-from requests.exceptions import ConnectionError as RequestsConnectionError
-from simple_salesforce.exceptions import SalesforceError
-from simple_settings import settings
-from werkzeug.wsgi import DispatcherMiddleware
-from uwsgidecorators import mulefunc
+from prometheus_client import make_wsgi_app
+
+from requests.exceptions import ConnectionError as RequestsConnectionError
from sf_notifier.helpers import alert_fields_and_action, create_file
from sf_notifier.salesforce.client import SESSION_FILE, SalesforceClient
-from sf_notifier.salesforce.settings import CASE_STATUS
+
+from simple_salesforce.exceptions import SalesforceError
+
+from simple_settings import settings
+
+from werkzeug.wsgi import DispatcherMiddleware
dictConfig(settings.LOGGING)
@@ -36,56 +52,6 @@
})
-@mulefunc
-def offload(action, fields):
- try:
- getattr(sf_cli, action)(*fields)
- except (SalesforceError, RequestsConnectionError) as err:
- msg = 'Salesforce request failure: {}.'.format(err)
- sf_cli.metrics['sf_error_count'].inc()
- app.logger.error(msg)
-
-
-def create_case_results(alert_ids):
- cases = {}
- is_error = False
-
- while len(alert_ids) > len(cases):
- for alert_id in alert_ids:
- case = sf_cli._registered_alerts.get(alert_id)
-
- if case is None:
- continue
-
- if case['id'] not in CASE_STATUS:
- cases[alert_id] = {}
- cases[alert_id]['case'] = case['id']
- if case['id'] == 'error':
- if sf_cli._feed_update_ready(case['last_update']):
- continue
- is_error = True
- cases[alert_id] = {}
- cases[alert_id]['error'] = case['error']
-
- time.sleep(0.2)
- return cases, is_error
-
-
-def close_case_results(alert_ids):
- # timeout is implicit error
- cases = {}
-
- while len(alert_ids) > 0:
- for alert_id in alert_ids:
- case = sf_cli._registered_alerts.get(alert_id)
-
- if case is None:
- cases[alert_id] = {'status': 'closed'}
- alert_ids.pop(alert_ids.index(alert_id))
- time.sleep(0.2)
- return cases, False
-
-
@app.route('/hook', methods=['POST'])
def webhook_receiver():
@@ -100,21 +66,31 @@
app.logger.info('Received requests: {}'.format(data))
- alert_ids = []
+ cases = []
for alert in data['alerts']:
- alert['labels']['env_id'] = sf_cli.environment
- alert_ids.append(sf_cli.get_alert_id(alert['labels']))
- fields, action = alert_fields_and_action(alert)
+ try:
+ alert['labels']['env_id'] = sf_cli.environment
+ fields, action = alert_fields_and_action(alert)
+ except KeyError as key:
+ msg = 'Alert misses {} key.'.format(key)
+ app.logger.error(msg)
+ return Response(json.dumps({'error': msg}),
+ status=400,
+ mimetype='application/json')
- offload(action, fields)
+ if fields:
+ try:
+ cases.append(getattr(sf_cli, action)(*fields))
+ except (SalesforceError, RequestsConnectionError) as err:
+ msg = 'Salesforce request failure: {}.'.format(err)
+ sf_cli.metrics['sf_error_count'].inc()
+ app.logger.error(msg)
+ return Response(json.dumps({'error': msg}),
+ status=500,
+ mimetype='application/json')
- cases, is_error = globals()[action + '_results'](alert_ids)
-
- if is_error:
- return Response(json.dumps(cases),
- status=500,
- mimetype='application/json')
-
+ if len(cases) == 1:
+ return jsonify(cases[0])
return jsonify(cases)
diff --git a/sf_notifier/tests/test_client.py b/sf_notifier/tests/test_client.py
index f505e68..059ea24 100644
--- a/sf_notifier/tests/test_client.py
+++ b/sf_notifier/tests/test_client.py
@@ -2,8 +2,7 @@
import pytest
-from sf_notifier.salesforce.client import SalesforceClient
-from sf_notifier.salesforce.exceptions import SfNotifierError
+from sf_notifier.salesforce.client import SalesforceClient, SfNotifierError
ENV_VARS = [