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 = [