4.2.0 policy-handler - periodic reconfigure

- reconfigure == periodically retrieve the policy-handler config
  from consul-kv and compare to previous config and subconfigs.
  If changed, reconfigure the subunits
- selectively change one or any settings for the following
  = catch_up timer interval
  = reconfigure timer interval
  = deployment-handler url and params (thread-safe)
  = policy-engine url and params (thread-safe)
  = web-socket url to policy-engine (through a callback)
- each subunit has its own Settings that keep track of changes
- try-catch and metrics around discovery - consul API
- hidden the secrets from logs
- froze the web-socket version to 0.49.0 because 0.50.0
  and 0.51.0 are broken - looking around for stable alternatives
- fixed-adapted the callbacks passed to the web-socket lib
  that changed its API in 0.49.0 and later
- log the stack on the exception occurring in the web-socket lib
- unit test refactoring

Change-Id: Id53bad59660a197f59d9aeb7c05ab761d1060cd0
Signed-off-by: Alex Shatov <alexs@att.com>
Issue-ID: DCAEGEN2-470
diff --git a/policyhandler/discovery.py b/policyhandler/discovery.py
index ce24c3d..4e6bc3d 100644
--- a/policyhandler/discovery.py
+++ b/policyhandler/discovery.py
@@ -25,6 +25,8 @@
 import requests
 
 from .customize import CustomizerUser
+from .onap.audit import AuditHttpCode, Metrics
+
 
 class DiscoveryClient(object):
     """talking to consul at http://consul:8500
@@ -40,56 +42,103 @@
         -p <outport>:<innerport>
         ${APPNAME}:latest
     """
+    CONSUL_ENTITY = "consul"
     CONSUL_SERVICE_MASK = "http://consul:8500/v1/catalog/service/{0}"
     CONSUL_KV_MASK = "http://consul:8500/v1/kv/{0}"
     _logger = logging.getLogger("policy_handler.discovery")
 
     @staticmethod
+    def _discover_service(audit, service_name, service_path):
+        """find the service record in consul"""
+        response = requests.get(service_path)
+        DiscoveryClient._logger.info(audit.info("response {} from {}: {}".format(
+            response.status_code, service_path, response.text)))
+
+        response.raise_for_status()
+        status_code = response.status_code
+        service = response.json()[0]
+        return (status_code,
+                CustomizerUser.get_customizer().get_service_url(audit, service_name, service))
+
+    @staticmethod
     def get_service_url(audit, service_name):
         """find the service record in consul"""
         service_path = DiscoveryClient.CONSUL_SERVICE_MASK.format(service_name)
-        log_line = "discover {0}".format(service_path)
-        DiscoveryClient._logger.info(log_line)
-        audit.info(log_line)
-        response = requests.get(service_path)
+        metrics = Metrics(aud_parent=audit, targetEntity=DiscoveryClient.CONSUL_ENTITY,
+                          targetServiceName=service_path)
 
-        log_line = "response {0} for {1}: {2}".format(
-            response.status_code, service_path, response.text)
-        DiscoveryClient._logger.info(log_line)
-        audit.info(log_line)
+        log_line = "get from {} at {}".format(DiscoveryClient.CONSUL_ENTITY, service_path)
 
-        response.raise_for_status()
+        DiscoveryClient._logger.info(metrics.metrics_start(log_line))
+        status_code = None
+        try:
+            (status_code,
+             service_url) = DiscoveryClient._discover_service(audit, service_name, service_path)
+        except Exception as ex:
+            error_code = (AuditHttpCode.SERVICE_UNAVAILABLE_ERROR.value
+                          if isinstance(ex, requests.exceptions.RequestException)
+                          else AuditHttpCode.SERVER_INTERNAL_ERROR.value)
+            error_msg = ("failed {}/{} to {} {}: {}".format(status_code, error_code, log_line,
+                                                            type(ex).__name__, str(ex)))
+            DiscoveryClient._logger.exception(error_msg)
+            metrics.set_http_status_code(error_code)
+            audit.set_http_status_code(error_code)
+            metrics.metrics(error_msg)
+            return None
 
-        service = response.json()
-        if not service:
-            log_line = "failed discover {0}".format(service_path)
-            DiscoveryClient._logger.error(log_line)
-            audit.error(log_line)
-            return
-        service = service[0]
-
-        service_url = CustomizerUser.get_customizer().get_service_url(audit, service_name, service)
         if not service_url:
-            log_line = "failed to get service_url for {0}".format(service_name)
-            DiscoveryClient._logger.error(log_line)
-            audit.error(log_line)
-            return
+            error_code = AuditHttpCode.DATA_ERROR.value
+            error_msg = "failed {}/{} to {}".format(status_code, error_code, log_line)
+            DiscoveryClient._logger.error(audit.error(error_msg))
+            metrics.set_http_status_code(error_code)
+            audit.set_http_status_code(error_code)
+            metrics.metrics(error_msg)
+            return None
 
-        log_line = "got service_url: {0} for {1}".format(service_url, service_name)
-        DiscoveryClient._logger.info(log_line)
-        audit.info(log_line)
+        log_line = "response {} {}".format(status_code, log_line)
+        DiscoveryClient._logger.info(audit.info("got service_url: {} after {}"
+                                                .format(service_url, log_line)))
+
+        metrics.set_http_status_code(status_code)
+        audit.set_http_status_code(status_code)
+        metrics.metrics(log_line)
         return service_url
 
     @staticmethod
-    def get_value(key):
-        """get the value for the key from consul-kv"""
-        response = requests.get(DiscoveryClient.CONSUL_KV_MASK.format(key))
+    def _get_value_from_kv(url):
+        """get the value from consul-kv at discovery url"""
+        response = requests.get(url)
         response.raise_for_status()
         data = response.json()
-        if not data:
-            DiscoveryClient._logger.error("failed get_value %s", key)
-            return
         value = base64.b64decode(data[0]["Value"]).decode("utf-8")
-        DiscoveryClient._logger.info("consul-kv key=%s value(%s) data=%s",
-                                     key, value, json.dumps(data))
-        return json.loads(value)
+        return response.status_code, json.loads(value)
+
+    @staticmethod
+    def get_value(audit, key):
+        """get the value for the key from consul-kv"""
+        discovery_url = DiscoveryClient.CONSUL_KV_MASK.format(key)
+        metrics = Metrics(aud_parent=audit, targetEntity=DiscoveryClient.CONSUL_ENTITY,
+                          targetServiceName=discovery_url)
+
+        log_line = "get from {} at {}".format(DiscoveryClient.CONSUL_ENTITY, discovery_url)
+
+        DiscoveryClient._logger.info(metrics.metrics_start(log_line))
+        try:
+            status_code, value = DiscoveryClient._get_value_from_kv(discovery_url)
+        except Exception as ex:
+            error_code = (AuditHttpCode.SERVICE_UNAVAILABLE_ERROR.value
+                          if isinstance(ex, requests.exceptions.RequestException)
+                          else AuditHttpCode.SERVER_INTERNAL_ERROR.value)
+            error_msg = ("failed {}/{} to {} {}: {}".format(status_code, error_code, log_line,
+                                                            type(ex).__name__, str(ex)))
+            DiscoveryClient._logger.exception(error_msg)
+            metrics.set_http_status_code(error_code)
+            audit.set_http_status_code(error_code)
+            metrics.metrics(error_msg)
+            return None
+
+        log_line = "response {} {}".format(status_code, log_line)
+        metrics.set_http_status_code(status_code)
+        audit.set_http_status_code(status_code)
+        metrics.metrics(log_line)
+        return value