2.2.1 dcaepolicyplugin and data types 79/39279/2
authorAlex Shatov <alexs@att.com>
Tue, 27 Mar 2018 21:12:31 +0000 (17:12 -0400)
committerAlex Shatov <alexs@att.com>
Tue, 27 Mar 2018 21:12:31 +0000 (17:12 -0400)
- trying to avoid changing code for k8s deployment of
  policy-handler with unknown url to MSB or policy-handler
  at the moment
- expecting optional manual population of the consul-kv
  with config data for dcaepolicyplugin

- when not found service for policy-handler in consul
  -- try finding config for "dcaepolicyplugin" in consul-kv
  -- the config structure is expected to contain
      url to policy_handler
- example of config value for key=dcaepolicyplugin:
{
    "dcaepolicyplugin" : {
        "policy_handler" : {
            "url" : "http://policy-handler:25577"
        }
    }
}
- still drop down to hardcoded default when this config
   not found in consul-kv

- added and refactored unit tests for discovery -- coverage 78%
- making code more PEP8 compliant

Change-Id: Ia176b54ed62631baa30d614785d1937023408ddf
Signed-off-by: Alex Shatov <alexs@att.com>
Issue-ID: DCAEGEN2-419

dcae-policy/README.md
dcae-policy/dcaepolicy-node-type.yaml
dcae-policy/dcaepolicyplugin/discovery.py
dcae-policy/dcaepolicyplugin/tasks.py
dcae-policy/pom.xml
dcae-policy/setup.py
dcae-policy/tests/log_ctx.py
dcae-policy/tests/mock_cloudify_ctx.py
dcae-policy/tests/mock_setup.py [new file with mode: 0644]
dcae-policy/tests/test_discovery.py [new file with mode: 0644]
dcae-policy/tests/test_tasks.py

index 042a550..2b06519 100644 (file)
@@ -8,6 +8,25 @@
 
 - node type for dcae.nodes.policy
 
+- node type for dcae.nodes.policies
+
+---
+
+## discovery of policy-handler
+
+- dcaepolicyplugin will first try finding the record of ```policy_handler``` in consul services.
+
+- if failed, it will try finding config for "dcaepolicyplugin" in consul-kv
+
+  -- the config structure is expected to contain url to policy_handler
+  -- example of config value for key=```dcaepolicyplugin```:
+
+```json
+{ "dcaepolicyplugin" : { "policy_handler" : { "url" : "http://policy-handler:25577" } } }
+```
+
+- if still not found, it will default to hardcoded url of ```http://policy-handler```
+
 ---
 
 ## Usage
@@ -16,7 +35,7 @@ import the dcaepolicy-node-type.yaml into your blueprint to use the dcae.nodes.t
 
 ```yaml
 imports:
-    - https://YOUR_NEXUS_RAW_SERVER/type_files/dcaepolicy/2.1.0/node-type.yaml
+    - https://YOUR_NEXUS_RAW_SERVER/type_files/dcaepolicy/2.2.1/node-type.yaml
 ```
 
 provide the value for policy_id property
index d174294..fdb29ed 100644 (file)
@@ -25,7 +25,7 @@ plugins:
   dcaepolicy:
     executor: 'central_deployment_agent'
     package_name: dcaepolicyplugin
-    package_version: 2.2.0
+    package_version: 2.2.1
 
 data_types:
     # the properties inside dcae.data.policy_filter are identical to /getConfig API of policy-engine except the requestID field.
index 45a061b..1faee08 100644 (file)
 
 """client to talk to consul on standard port 8500"""
 
-import requests
+import base64
+import json
 
+import requests
 from cloudify import ctx
 
 # it is safe to assume that consul agent is at localhost:8500 along with cloudify manager
 CONSUL_SERVICE_URL = "http://localhost:8500/v1/catalog/service/{0}"
+CONSUL_KV_MASK = "http://localhost:8500/v1/kv/{0}"
+
 
 def discover_service_url(service_name):
     """find the service record in consul"""
-    service_url_url = CONSUL_SERVICE_URL.format(service_name)
-    ctx.logger.info("getting service_url at {0}".format(service_url_url))
+    service_url = CONSUL_SERVICE_URL.format(service_name)
+    ctx.logger.info("getting service_url at {0}".format(service_url))
+
+    response = requests.get(service_url)
+
+    ctx.logger.info("got {0} for service_url at {1} response: {2}"
+                    .format(response.status_code, service_url, response.text))
+
+    if response.status_code != requests.codes.ok:
+        return
+
+    resp_json = response.json()
+    if resp_json:
+        service = resp_json[0]
+        return "http://{0}:{1}".format(service["ServiceAddress"], service["ServicePort"])
+
+
+def discover_value(key):
+    """get the value for the key from consul-kv"""
+    kv_url = CONSUL_KV_MASK.format(key)
+    ctx.logger.info("getting kv at {0}".format(kv_url))
+
+    response = requests.get(kv_url)
 
-    response = requests.get(service_url_url)
+    ctx.logger.info("got {0} for kv at {1} response: {2}"
+                    .format(response.status_code, kv_url, response.text))
 
-    ctx.logger.info("got service_url at {0} status({1}) response: {2}"
-                    .format(service_url_url, response.status_code, response.text))
+    if response.status_code != requests.codes.ok:
+        return
 
-    if response.status_code == requests.codes.ok:
-        resp_json = response.json()
-        if resp_json:
-            service = resp_json[0]
-            return "http://{0}:{1}".format(service["ServiceAddress"], service["ServicePort"])
+    data = response.json()
+    if not data:
+        ctx.logger.error("failed discover_value %s", key)
+        return
+    value = base64.b64decode(data[0]["Value"]).decode("utf-8")
+    ctx.logger.info("consul-kv key=%s value(%s) data=%s",
+                    key, value, json.dumps(data))
+    return json.loads(value)
index b3a29aa..bbf3ec1 100644 (file)
 
 """tasks are the cloudify operations invoked on interfaces defined in the blueprint"""
 
-import json
-import uuid
 import copy
+import json
 import traceback
-import requests
+import uuid
 
+import requests
 from cloudify import ctx
-from cloudify.decorators import operation
 from cloudify.context import NODE_INSTANCE
+from cloudify.decorators import operation
 from cloudify.exceptions import NonRecoverableError
 
-from .discovery import discover_service_url
+from .discovery import discover_service_url, discover_value
 
+DCAE_POLICY_PLUGIN = "dcaepolicyplugin"
 POLICY_ID = 'policy_id'
 POLICY_REQUIRED = 'policy_required'
 POLICY_BODY = 'policy_body'
@@ -45,6 +46,7 @@ DCAE_POLICIES_TYPE = 'dcae.nodes.policies'
 DCAE_POLICY_TYPES = [DCAE_POLICY_TYPE, DCAE_POLICIES_TYPE]
 CONFIG_ATTRIBUTES = "configAttributes"
 
+
 class PolicyHandler(object):
     """talk to policy-handler"""
     SERVICE_NAME_POLICY_HANDLER = "policy_handler"
@@ -60,8 +62,27 @@ class PolicyHandler(object):
             return
 
         PolicyHandler._url = discover_service_url(PolicyHandler.SERVICE_NAME_POLICY_HANDLER)
-        if not PolicyHandler._url:
-            PolicyHandler._url = PolicyHandler.DEFAULT_URL
+        if PolicyHandler._url:
+            return
+
+        config = discover_value(DCAE_POLICY_PLUGIN)
+        if config and isinstance(config, dict):
+            # expected structure for the config value for dcaepolicyplugin key
+            # {
+            #     "dcaepolicyplugin" : {
+            #         "policy_handler" : {
+            #             "target_entity" : "policy_handler",
+            #             "url" : "http://policy-handler:25577"
+            #         }
+            #     }
+            # }
+            PolicyHandler._url = config.get(DCAE_POLICY_PLUGIN, {}) \
+                .get(PolicyHandler.SERVICE_NAME_POLICY_HANDLER, {}).get("url")
+
+        if PolicyHandler._url:
+            return
+
+        PolicyHandler._url = PolicyHandler.DEFAULT_URL
 
     @staticmethod
     def get_latest_policy(policy_id):
@@ -93,7 +114,7 @@ class PolicyHandler(object):
             PolicyHandler.X_ECOMP_REQUESTID: policy_filter.get(REQUEST_ID, str(uuid.uuid4()))
         }
 
-        ctx.logger.info("finding the latest polices from {0} by {1} headers={2}".format( \
+        ctx.logger.info("finding the latest polices from {0} by {1} headers={2}".format(
             ph_path, json.dumps(policy_filter), json.dumps(headers)))
 
         res = requests.post(ph_path, json=policy_filter, headers=headers)
@@ -106,6 +127,7 @@ class PolicyHandler(object):
         res.raise_for_status()
         return res.json().get(LATEST_POLICIES)
 
+
 def _policy_get():
     """
     dcae.nodes.policy -
@@ -143,6 +165,7 @@ def _policy_get():
         ctx.instance.runtime_properties[POLICY_BODY] = policy[POLICY_BODY]
     return True
 
+
 def _fix_policy_filter(policy_filter):
     if CONFIG_ATTRIBUTES in policy_filter:
         config_attributes = policy_filter.get(CONFIG_ATTRIBUTES)
@@ -159,6 +182,7 @@ def _fix_policy_filter(policy_filter):
             ctx.logger.warn("unexpected %s: %s", CONFIG_ATTRIBUTES, config_attributes)
         del policy_filter[CONFIG_ATTRIBUTES]
 
+
 def _policies_find():
     """
     dcae.nodes.policies -
@@ -195,6 +219,7 @@ def _policies_find():
 
     return True
 
+
 #########################################################
 @operation
 def policy_get(**kwargs):
index 90a0e7a..b46e9b8 100644 (file)
@@ -28,7 +28,7 @@ ECOMP is a trademark and service mark of AT&T Intellectual Property.
   <groupId>org.onap.dcaegen2.platform.plugins</groupId>
   <artifactId>dcae-policy</artifactId>
   <name>dcae-policy-plugin</name>
-  <version>2.2.0-SNAPSHOT</version>
+  <version>2.2.1-SNAPSHOT</version>
   <url>http://maven.apache.org</url>
   <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
index 8c54e10..8072873 100644 (file)
@@ -23,7 +23,7 @@ from setuptools import setup
 setup(
     name='dcaepolicyplugin',
     description='Cloudify plugin for dcae.nodes.policy node to retrieve the policy config',
-    version="2.2.0",
+    version="2.2.1",
     author='Alex Shatov',
     packages=['dcaepolicyplugin'],
     install_requires=[
index 0d82687..7685893 100644 (file)
@@ -25,6 +25,7 @@ from functools import wraps
 from cloudify import ctx
 from cloudify.context import NODE_INSTANCE, RELATIONSHIP_INSTANCE
 
+
 class CtxLogger(object):
     """static class for logging cloudify context ctx"""
     @staticmethod
index eab7ab1..fb52b43 100644 (file)
@@ -18,7 +18,8 @@
 
 """mock cloudify context with relationships and type_hierarchy"""
 
-from cloudify.mocks import MockCloudifyContext, MockNodeInstanceContext, MockNodeContext
+from cloudify.mocks import (MockCloudifyContext, MockNodeContext,
+                            MockNodeInstanceContext)
 
 TARGET_NODE_ID = "target_node_id"
 TARGET_NODE_NAME = "target_node_name"
diff --git a/dcae-policy/tests/mock_setup.py b/dcae-policy/tests/mock_setup.py
new file mode 100644 (file)
index 0000000..c74b236
--- /dev/null
@@ -0,0 +1,156 @@
+# ================================================================================
+# Copyright (c) 2018 AT&T Intellectual Property. 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.
+# ============LICENSE_END=========================================================
+#
+# ECOMP is a trademark and service mark of AT&T Intellectual Property.
+
+"""unit tests for tasks in dcaepolicyplugin"""
+
+import json
+import logging
+from datetime import datetime, timedelta
+
+from tests.mock_cloudify_ctx import MockCloudifyContextFull
+
+LOG_FILE = 'logs/test_dcaepolicyplugin.log'
+POLICY_ID = 'policy_id'
+POLICY_VERSION = "policyVersion"
+POLICY_NAME = "policyName"
+POLICY_BODY = 'policy_body'
+POLICY_CONFIG = 'config'
+CONFIG_NAME = "ConfigName"
+MONKEYED_POLICY_ID = 'monkeyed.Config_peach'
+
+RUN_TS = datetime.utcnow()
+
+
+class MonkeyedLogHandler(object):
+    """keep the shared logger handler here"""
+    _log_handler = None
+
+    @staticmethod
+    def add_handler_to(logger):
+        """adds the local handler to the logger"""
+        if not MonkeyedLogHandler._log_handler:
+            MonkeyedLogHandler._log_handler = logging.FileHandler(LOG_FILE)
+            MonkeyedLogHandler._log_handler.setLevel(logging.DEBUG)
+            formatter = logging.Formatter(
+                fmt='%(asctime)s.%(msecs)03d %(levelname)+8s ' +
+                    '%(threadName)s %(name)s.%(funcName)s: %(message)s',
+                datefmt='%Y%m%d_%H%M%S')
+            MonkeyedLogHandler._log_handler.setFormatter(formatter)
+        logger.addHandler(MonkeyedLogHandler._log_handler)
+
+
+class MonkeyedPolicyBody(object):
+    """policy body that policy-engine returns"""
+    @staticmethod
+    def create_policy_body(policy_id, policy_version=1):
+        """returns a fake policy-body"""
+        prev_ver = policy_version - 1
+        timestamp = RUN_TS + timedelta(hours=prev_ver)
+
+        prev_ver = str(prev_ver)
+        this_ver = str(policy_version)
+        config = {
+            "policy_updated_from_ver": prev_ver,
+            "policy_updated_to_ver": this_ver,
+            "policy_hello": "world!",
+            "policy_updated_ts": timestamp.isoformat()[:-3] + 'Z',
+            "updated_policy_id": policy_id
+        }
+        return {
+            "policyConfigMessage": "Config Retrieved! ",
+            "policyConfigStatus": "CONFIG_RETRIEVED",
+            "type": "JSON",
+            POLICY_NAME: "{0}.{1}.xml".format(policy_id, this_ver),
+            POLICY_VERSION: this_ver,
+            POLICY_CONFIG: config,
+            "matchingConditions": {
+                "ONAPName": "DCAE",
+                CONFIG_NAME: "alex_config_name"
+            },
+            "responseAttributes": {},
+            "property": None
+        }
+
+    @staticmethod
+    def create_policy(policy_id, policy_version=1):
+        """returns the whole policy object for policy_id and policy_version"""
+        return {
+            POLICY_ID: policy_id,
+            POLICY_BODY: MonkeyedPolicyBody.create_policy_body(policy_id, policy_version)
+        }
+
+    @staticmethod
+    def is_the_same_dict(policy_body_1, policy_body_2):
+        """check whether both policy_body objects are the same"""
+        if not isinstance(policy_body_1, dict) or not isinstance(policy_body_2, dict):
+            return False
+        for key in policy_body_1.keys():
+            if key not in policy_body_2:
+                return False
+
+            val_1 = policy_body_1[key]
+            val_2 = policy_body_2[key]
+            if isinstance(val_1, dict) \
+            and not MonkeyedPolicyBody.is_the_same_dict(val_1, val_2):
+                return False
+            if (val_1 is None and val_2 is not None) \
+            or (val_1 is not None and val_2 is None) \
+            or (val_1 != val_2):
+                return False
+        return True
+
+
+class MonkeyedResponse(object):
+    """Monkey response"""
+    def __init__(self, full_path, headers=None, resp_json=None):
+        self.full_path = full_path
+        self.status_code = 200
+        self.headers = headers or {}
+        self.resp_json = resp_json
+        self.text = json.dumps(resp_json or {})
+
+    def json(self):
+        """returns json of response"""
+        return self.resp_json
+
+    def raise_for_status(self):
+        """always happy"""
+        pass
+
+
+class MonkeyedNode(object):
+    """node in cloudify"""
+    BLUEPRINT_ID = 'test_dcae_policy_bp_id'
+    DEPLOYMENT_ID = 'test_dcae_policy_dpl_id'
+    EXECUTION_ID = 'test_dcae_policy_exe_id'
+
+    def __init__(self, node_id, node_name, node_type, properties, relationships=None):
+        self.node_id = node_id
+        self.node_name = node_name
+        self.ctx = MockCloudifyContextFull(
+            node_id=self.node_id,
+            node_name=self.node_name,
+            node_type=node_type,
+            blueprint_id=MonkeyedNode.BLUEPRINT_ID,
+            deployment_id=MonkeyedNode.DEPLOYMENT_ID,
+            execution_id=MonkeyedNode.EXECUTION_ID,
+            properties=properties,
+            relationships=relationships
+        )
+        MonkeyedLogHandler.add_handler_to(self.ctx.logger)
+
diff --git a/dcae-policy/tests/test_discovery.py b/dcae-policy/tests/test_discovery.py
new file mode 100644 (file)
index 0000000..893b7ce
--- /dev/null
@@ -0,0 +1,127 @@
+# ================================================================================
+# Copyright (c) 2018 AT&T Intellectual Property. 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.
+# ============LICENSE_END=========================================================
+#
+# ECOMP is a trademark and service mark of AT&T Intellectual Property.
+
+"""unit tests for discovery in dcaepolicyplugin"""
+
+import base64
+import json
+
+import pytest
+from cloudify.state import current_ctx
+
+from dcaepolicyplugin import discovery, tasks
+from tests.log_ctx import CtxLogger
+from tests.mock_cloudify_ctx import MockCloudifyContextFull
+from tests.mock_setup import (MONKEYED_POLICY_ID, POLICY_ID, MonkeyedNode,
+                              MonkeyedResponse)
+
+POLICY_HANDLER_FROM_KV = "http:policy_handler_from_kv:25577"
+
+
+def monkeyed_discovery_get_failure(full_path):
+    """monkeypatch for the GET to consul"""
+    return MonkeyedResponse(full_path)
+
+
+def test_discovery_failure(monkeypatch):
+    """test finding policy-handler in consul"""
+    monkeypatch.setattr('requests.get', monkeyed_discovery_get_failure)
+
+    node_policy = MonkeyedNode(
+        'test_dcae_policy_node_id',
+        'test_dcae_policy_node_name',
+        tasks.DCAE_POLICY_TYPE,
+        {POLICY_ID: MONKEYED_POLICY_ID}
+    )
+    try:
+        current_ctx.set(node_policy.ctx)
+        tasks.PolicyHandler._lazy_init()
+        assert tasks.PolicyHandler.DEFAULT_URL == tasks.PolicyHandler._url
+
+    finally:
+        tasks.PolicyHandler._url = None
+        MockCloudifyContextFull.clear()
+        current_ctx.clear()
+
+
+def monkeyed_discovery_get_kv(full_path):
+    """monkeypatch for the GET to consul"""
+    if full_path.startswith(discovery.CONSUL_SERVICE_URL.format("")):
+        return MonkeyedResponse(full_path)
+
+    if full_path.startswith(discovery.CONSUL_KV_MASK.format("")):
+        value = base64.b64encode(json.dumps(
+            {tasks.DCAE_POLICY_PLUGIN: {
+                tasks.PolicyHandler.SERVICE_NAME_POLICY_HANDLER: {
+                    "url": POLICY_HANDLER_FROM_KV}}}
+        ))
+        return MonkeyedResponse(full_path, {}, [{"Value": value}])
+
+    return MonkeyedResponse(full_path)
+
+
+def test_discovery_kv(monkeypatch):
+    """test finding policy-handler in consul"""
+    monkeypatch.setattr('requests.get', monkeyed_discovery_get_kv)
+
+    node_policy = MonkeyedNode(
+        'test_dcae_policy_node_id',
+        'test_dcae_policy_node_name',
+        tasks.DCAE_POLICY_TYPE,
+        {POLICY_ID: MONKEYED_POLICY_ID}
+    )
+    try:
+        current_ctx.set(node_policy.ctx)
+        tasks.PolicyHandler._lazy_init()
+        assert POLICY_HANDLER_FROM_KV == tasks.PolicyHandler._url
+
+    finally:
+        tasks.PolicyHandler._url = None
+        MockCloudifyContextFull.clear()
+        current_ctx.clear()
+
+
+def monkeyed_discovery_get(full_path):
+    """monkeypatch for the GET to consul"""
+    return MonkeyedResponse(full_path, {},
+        [{"ServiceAddress": "monkey-policy-handler-address", "ServicePort": "9999"}])
+
+
+def test_discovery(monkeypatch):
+    """test finding policy-handler in consul"""
+    monkeypatch.setattr('requests.get', monkeyed_discovery_get)
+
+    node_policy = MonkeyedNode(
+        'test_dcae_policy_node_id',
+        'test_dcae_policy_node_name',
+        tasks.DCAE_POLICY_TYPE,
+        {POLICY_ID: MONKEYED_POLICY_ID}
+    )
+
+    try:
+        current_ctx.set(node_policy.ctx)
+        expected = "http://monkey-policy-handler-address:9999"
+        CtxLogger.log_ctx_info("before PolicyHandler._lazy_init")
+        tasks.PolicyHandler._lazy_init()
+        CtxLogger.log_ctx_info("after PolicyHandler._lazy_init")
+        assert expected == tasks.PolicyHandler._url
+
+    finally:
+        tasks.PolicyHandler._url = None
+        MockCloudifyContextFull.clear()
+        current_ctx.clear()
index b9b69b9..9f9121d 100644 (file)
@@ -19,8 +19,6 @@
 """unit tests for tasks in dcaepolicyplugin"""
 
 import json
-import logging
-from datetime import datetime, timedelta
 
 import pytest
 from cloudify.exceptions import NonRecoverableError
@@ -30,194 +28,20 @@ from dcaepolicyplugin import tasks
 from tests.log_ctx import CtxLogger
 from tests.mock_cloudify_ctx import (TARGET_NODE_ID, TARGET_NODE_NAME,
                                      MockCloudifyContextFull)
+from tests.mock_setup import (CONFIG_NAME, MONKEYED_POLICY_ID, POLICY_BODY,
+                              POLICY_ID, POLICY_NAME, MonkeyedNode,
+                              MonkeyedPolicyBody, MonkeyedResponse)
 
-POLICY_ID = 'policy_id'
-POLICY_VERSION = "policyVersion"
-POLICY_NAME = "policyName"
-POLICY_BODY = 'policy_body'
-POLICY_CONFIG = 'config'
-LATEST_POLICIES = "latest_policies"
-CONFIG_NAME = "ConfigName"
-
-MONKEYED_POLICY_ID = 'monkeyed.Config_peach'
-LOG_FILE = 'logs/test_dcaepolicyplugin.log'
-
-RUN_TS = datetime.utcnow()
-
-class MonkeyedLogHandler(object):
-    """keep the shared logger handler here"""
-    _log_handler = None
-
-    @staticmethod
-    def add_handler_to(logger):
-        """adds the local handler to the logger"""
-        if not MonkeyedLogHandler._log_handler:
-            MonkeyedLogHandler._log_handler = logging.FileHandler(LOG_FILE)
-            MonkeyedLogHandler._log_handler.setLevel(logging.DEBUG)
-            formatter = logging.Formatter(
-                fmt='%(asctime)s.%(msecs)03d %(levelname)+8s ' + \
-                    '%(threadName)s %(name)s.%(funcName)s: %(message)s', \
-                datefmt='%Y%m%d_%H%M%S')
-            MonkeyedLogHandler._log_handler.setFormatter(formatter)
-        logger.addHandler(MonkeyedLogHandler._log_handler)
-
-class MonkeyedPolicyBody(object):
-    """policy body that policy-engine returns"""
-    @staticmethod
-    def create_policy_body(policy_id, policy_version=1):
-        """returns a fake policy-body"""
-        prev_ver = policy_version - 1
-        timestamp = RUN_TS + timedelta(hours=prev_ver)
-
-        prev_ver = str(prev_ver)
-        this_ver = str(policy_version)
-        config = {
-            "policy_updated_from_ver": prev_ver,
-            "policy_updated_to_ver": this_ver,
-            "policy_hello": "world!",
-            "policy_updated_ts": timestamp.isoformat()[:-3] + 'Z',
-            "updated_policy_id": policy_id
-        }
-        return {
-            "policyConfigMessage": "Config Retrieved! ",
-            "policyConfigStatus": "CONFIG_RETRIEVED",
-            "type": "JSON",
-            POLICY_NAME: "{0}.{1}.xml".format(policy_id, this_ver),
-            POLICY_VERSION: this_ver,
-            POLICY_CONFIG: config,
-            "matchingConditions": {
-                "ONAPName": "DCAE",
-                CONFIG_NAME: "alex_config_name"
-            },
-            "responseAttributes": {},
-            "property": None
-        }
-
-    @staticmethod
-    def create_policy(policy_id, policy_version=1):
-        """returns the whole policy object for policy_id and policy_version"""
-        return {
-            POLICY_ID : policy_id,
-            POLICY_BODY : MonkeyedPolicyBody.create_policy_body(policy_id, policy_version)
-        }
-
-    @staticmethod
-    def is_the_same_dict(policy_body_1, policy_body_2):
-        """check whether both policy_body objects are the same"""
-        if not isinstance(policy_body_1, dict) or not isinstance(policy_body_2, dict):
-            return False
-        for key in policy_body_1.keys():
-            if key not in policy_body_2:
-                return False
-
-            val_1 = policy_body_1[key]
-            val_2 = policy_body_2[key]
-            if isinstance(val_1, dict) \
-            and not MonkeyedPolicyBody.is_the_same_dict(val_1, val_2):
-                return False
-            if (val_1 is None and val_2 is not None) \
-            or (val_1 is not None and val_2 is None) \
-            or (val_1 != val_2):
-                return False
-        return True
-
-class MonkeyedResponse(object):
-    """Monkey response"""
-    def __init__(self, full_path, headers=None, resp_json=None):
-        self.full_path = full_path
-        self.status_code = 200
-        self.headers = headers
-        self.resp_json = resp_json
-        self.text = json.dumps(resp_json or {})
-
-    def json(self):
-        """returns json of response"""
-        return self.resp_json
-
-    def raise_for_status(self):
-        """always happy"""
-        pass
-
-class MonkeyedNode(object):
-    """node in cloudify"""
-    BLUEPRINT_ID = 'test_dcae_policy_bp_id'
-    DEPLOYMENT_ID = 'test_dcae_policy_dpl_id'
-    EXECUTION_ID = 'test_dcae_policy_exe_id'
-
-    def __init__(self, node_id, node_name, node_type, properties, relationships=None):
-        self.node_id = node_id
-        self.node_name = node_name
-        self.ctx = MockCloudifyContextFull(
-            node_id=self.node_id,
-            node_name=self.node_name,
-            node_type=node_type,
-            blueprint_id=MonkeyedNode.BLUEPRINT_ID,
-            deployment_id=MonkeyedNode.DEPLOYMENT_ID,
-            execution_id=MonkeyedNode.EXECUTION_ID,
-            properties=properties,
-            relationships=relationships
-        )
-        MonkeyedLogHandler.add_handler_to(self.ctx.logger)
-
-def monkeyed_discovery_get_failure(full_path):
-    """monkeypatch for the GET to consul"""
-    return MonkeyedResponse(full_path, {}, None)
-
-def test_discovery_failure(monkeypatch):
-    """test finding policy-handler in consul"""
-    monkeypatch.setattr('requests.get', monkeyed_discovery_get_failure)
-
-    node_policy = MonkeyedNode(
-        'test_dcae_policy_node_id',
-        'test_dcae_policy_node_name',
-        tasks.DCAE_POLICY_TYPE,
-        {POLICY_ID: MONKEYED_POLICY_ID}
-    )
-    try:
-        current_ctx.set(node_policy.ctx)
-        tasks.PolicyHandler._lazy_init()
-        assert tasks.PolicyHandler.DEFAULT_URL == tasks.PolicyHandler._url
-
-    finally:
-        tasks.PolicyHandler._url = None
-        MockCloudifyContextFull.clear()
-        current_ctx.clear()
-
-def monkeyed_discovery_get(full_path):
-    """monkeypatch for the GET to consul"""
-    return MonkeyedResponse(full_path, {}, \
-        [{"ServiceAddress":"monkey-policy-handler-address", "ServicePort": "9999"}])
-
-def test_discovery(monkeypatch):
-    """test finding policy-handler in consul"""
-    monkeypatch.setattr('requests.get', monkeyed_discovery_get)
-
-    node_policy = MonkeyedNode(
-        'test_dcae_policy_node_id',
-        'test_dcae_policy_node_name',
-        tasks.DCAE_POLICY_TYPE,
-        {POLICY_ID: MONKEYED_POLICY_ID}
-    )
 
-    try:
-        current_ctx.set(node_policy.ctx)
-        expected = "http://monkey-policy-handler-address:9999"
-        CtxLogger.log_ctx_info("before PolicyHandler._lazy_init")
-        tasks.PolicyHandler._lazy_init()
-        CtxLogger.log_ctx_info("after PolicyHandler._lazy_init")
-        assert expected == tasks.PolicyHandler._url
-
-    finally:
-        tasks.PolicyHandler._url = None
-        MockCloudifyContextFull.clear()
-        current_ctx.clear()
+LATEST_POLICIES = "latest_policies"
 
 
 def monkeyed_policy_handler_get(full_path, headers=None):
     """monkeypatch for the GET to policy-engine"""
-    return MonkeyedResponse(full_path, headers, \
+    return MonkeyedResponse(full_path, headers,
         MonkeyedPolicyBody.create_policy(MONKEYED_POLICY_ID))
 
+
 def test_policy_get(monkeypatch):
     """test policy_get operation on dcae.nodes.policy node"""
     tasks.PolicyHandler._url = tasks.PolicyHandler.DEFAULT_URL
@@ -237,7 +61,7 @@ def test_policy_get(monkeypatch):
         CtxLogger.log_ctx_info("after policy_get")
 
         expected = {
-            POLICY_BODY : MonkeyedPolicyBody.create_policy_body(MONKEYED_POLICY_ID)
+            POLICY_BODY: MonkeyedPolicyBody.create_policy_body(MONKEYED_POLICY_ID)
         }
         result = node_policy.ctx.instance.runtime_properties
         node_policy.ctx.logger.info("expected runtime_properties: {0}".format(
@@ -246,7 +70,7 @@ def test_policy_get(monkeypatch):
         assert MonkeyedPolicyBody.is_the_same_dict(result, expected)
         assert MonkeyedPolicyBody.is_the_same_dict(expected, result)
 
-        node_ms = MonkeyedNode('test_ms_id', 'test_ms_name', "ms.nodes.type", None, \
+        node_ms = MonkeyedNode('test_ms_id', 'test_ms_name', "ms.nodes.type", None,
             [{TARGET_NODE_ID: node_policy.node_id,
               TARGET_NODE_NAME: node_policy.node_name}])
         current_ctx.set(node_ms.ctx)
@@ -260,12 +84,14 @@ def test_policy_get(monkeypatch):
         MockCloudifyContextFull.clear()
         current_ctx.clear()
 
+
 def monkeyed_policy_handler_find(full_path, json, headers):
     """monkeypatch for the GET to policy-engine"""
-    return MonkeyedResponse(full_path, headers, \
+    return MonkeyedResponse(full_path, headers,
         {LATEST_POLICIES: {
             MONKEYED_POLICY_ID: MonkeyedPolicyBody.create_policy(MONKEYED_POLICY_ID)}})
 
+
 def test_policies_find(monkeypatch):
     """test policy_get operation on dcae.nodes.policies node"""
     tasks.PolicyHandler._url = tasks.PolicyHandler.DEFAULT_URL
@@ -302,10 +128,10 @@ def test_policies_find(monkeypatch):
         assert MonkeyedPolicyBody.is_the_same_dict(result, expected)
         assert MonkeyedPolicyBody.is_the_same_dict(expected, result)
 
-        node_ms_multi = MonkeyedNode('test_ms_multi_id', 'test_ms_multi_name', "ms.nodes.type", \
-            None, \
-            [{TARGET_NODE_ID: node_policies.node_id,
-              TARGET_NODE_NAME: node_policies.node_name}])
+        node_ms_multi = MonkeyedNode('test_ms_multi_id', 'test_ms_multi_name', "ms.nodes.type",
+                                     None,
+                                     [{TARGET_NODE_ID: node_policies.node_id,
+                                       TARGET_NODE_NAME: node_policies.node_name}])
         current_ctx.set(node_ms_multi.ctx)
         CtxLogger.log_ctx_info("ctx of node_ms_multi not policy type")
         with pytest.raises(NonRecoverableError) as excinfo: