From 768c68b6fa25ab2faa9f7dbffdae2cbb2bd6e218 Mon Sep 17 00:00:00 2001 From: Alex Shatov Date: Tue, 27 Mar 2018 17:12:31 -0400 Subject: 2.2.1 dcaepolicyplugin and data types - 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 Issue-ID: DCAEGEN2-419 --- dcae-policy/README.md | 21 ++- dcae-policy/dcaepolicy-node-type.yaml | 2 +- dcae-policy/dcaepolicyplugin/discovery.py | 51 ++++++-- dcae-policy/dcaepolicyplugin/tasks.py | 41 ++++-- dcae-policy/pom.xml | 2 +- dcae-policy/setup.py | 2 +- dcae-policy/tests/log_ctx.py | 1 + dcae-policy/tests/mock_cloudify_ctx.py | 3 +- dcae-policy/tests/mock_setup.py | 156 +++++++++++++++++++++++ dcae-policy/tests/test_discovery.py | 127 +++++++++++++++++++ dcae-policy/tests/test_tasks.py | 204 +++--------------------------- 11 files changed, 397 insertions(+), 213 deletions(-) create mode 100644 dcae-policy/tests/mock_setup.py create mode 100644 dcae-policy/tests/test_discovery.py diff --git a/dcae-policy/README.md b/dcae-policy/README.md index 042a550..2b06519 100644 --- a/dcae-policy/README.md +++ b/dcae-policy/README.md @@ -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 diff --git a/dcae-policy/dcaepolicy-node-type.yaml b/dcae-policy/dcaepolicy-node-type.yaml index d174294..fdb29ed 100644 --- a/dcae-policy/dcaepolicy-node-type.yaml +++ b/dcae-policy/dcaepolicy-node-type.yaml @@ -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. diff --git a/dcae-policy/dcaepolicyplugin/discovery.py b/dcae-policy/dcaepolicyplugin/discovery.py index 45a061b..1faee08 100644 --- a/dcae-policy/dcaepolicyplugin/discovery.py +++ b/dcae-policy/dcaepolicyplugin/discovery.py @@ -18,25 +18,54 @@ """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) diff --git a/dcae-policy/dcaepolicyplugin/tasks.py b/dcae-policy/dcaepolicyplugin/tasks.py index b3a29aa..bbf3ec1 100644 --- a/dcae-policy/dcaepolicyplugin/tasks.py +++ b/dcae-policy/dcaepolicyplugin/tasks.py @@ -18,19 +18,20 @@ """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): diff --git a/dcae-policy/pom.xml b/dcae-policy/pom.xml index 90a0e7a..b46e9b8 100644 --- a/dcae-policy/pom.xml +++ b/dcae-policy/pom.xml @@ -28,7 +28,7 @@ ECOMP is a trademark and service mark of AT&T Intellectual Property. org.onap.dcaegen2.platform.plugins dcae-policy dcae-policy-plugin - 2.2.0-SNAPSHOT + 2.2.1-SNAPSHOT http://maven.apache.org UTF-8 diff --git a/dcae-policy/setup.py b/dcae-policy/setup.py index 8c54e10..8072873 100644 --- a/dcae-policy/setup.py +++ b/dcae-policy/setup.py @@ -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=[ diff --git a/dcae-policy/tests/log_ctx.py b/dcae-policy/tests/log_ctx.py index 0d82687..7685893 100644 --- a/dcae-policy/tests/log_ctx.py +++ b/dcae-policy/tests/log_ctx.py @@ -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 diff --git a/dcae-policy/tests/mock_cloudify_ctx.py b/dcae-policy/tests/mock_cloudify_ctx.py index eab7ab1..fb52b43 100644 --- a/dcae-policy/tests/mock_cloudify_ctx.py +++ b/dcae-policy/tests/mock_cloudify_ctx.py @@ -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 index 0000000..c74b236 --- /dev/null +++ b/dcae-policy/tests/mock_setup.py @@ -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 index 0000000..893b7ce --- /dev/null +++ b/dcae-policy/tests/test_discovery.py @@ -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() diff --git a/dcae-policy/tests/test_tasks.py b/dcae-policy/tests/test_tasks.py index b9b69b9..9f9121d 100644 --- a/dcae-policy/tests/test_tasks.py +++ b/dcae-policy/tests/test_tasks.py @@ -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: -- cgit 1.2.3-korg