From 62fd5bed6a6f8ff8c6b159e38d7462edda0cc3b0 Mon Sep 17 00:00:00 2001 From: alex_sh Date: Thu, 24 Aug 2017 11:44:59 -0400 Subject: added python-dcae-policy to utils Change-Id: Iefba53b3fa219ff799828e5128abd7503d0fe75c Issue-Id: DCAEGEN2-80 Signed-off-by: Alex Shatov --- .gitignore | 70 +++++ python-dcae-policy/LICENSE.txt | 30 ++ python-dcae-policy/MANIFEST.in | 1 + python-dcae-policy/README.md | 307 +++++++++++++++++++++ python-dcae-policy/dcaepolicy/__init__.py | 22 ++ .../dcaepolicy/dcae_consul_client.py | 45 +++ python-dcae-policy/dcaepolicy/dcae_policy.py | 289 +++++++++++++++++++ python-dcae-policy/dev_run.sh | 47 ++++ python-dcae-policy/nexus_pypi.md | 26 ++ python-dcae-policy/requirements.txt | 2 + python-dcae-policy/setup.py | 41 +++ 11 files changed, 880 insertions(+) create mode 100644 .gitignore create mode 100644 python-dcae-policy/LICENSE.txt create mode 100644 python-dcae-policy/MANIFEST.in create mode 100644 python-dcae-policy/README.md create mode 100644 python-dcae-policy/dcaepolicy/__init__.py create mode 100644 python-dcae-policy/dcaepolicy/dcae_consul_client.py create mode 100644 python-dcae-policy/dcaepolicy/dcae_policy.py create mode 100644 python-dcae-policy/dev_run.sh create mode 100644 python-dcae-policy/nexus_pypi.md create mode 100644 python-dcae-policy/requirements.txt create mode 100644 python-dcae-policy/setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3b3b613 --- /dev/null +++ b/.gitignore @@ -0,0 +1,70 @@ +.vscode/ +.cloudify +*.swp +*.swn +*.swo +.DS_Store +.project +.pydevproject +venv + + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +logs/ diff --git a/python-dcae-policy/LICENSE.txt b/python-dcae-policy/LICENSE.txt new file mode 100644 index 0000000..45ec201 --- /dev/null +++ b/python-dcae-policy/LICENSE.txt @@ -0,0 +1,30 @@ +============LICENSE_START======================================================= +org.onap.dcae +================================================================================ +Copyright (c) 2017 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. + +Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. +================================================================================ +Licensed under the Creative Commons License, Attribution 4.0 Intl. (the "License"); +you may not use this documentation except in compliance with the License. +You may obtain a copy of the License at + https://creativecommons.org/licenses/by/4.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/python-dcae-policy/MANIFEST.in b/python-dcae-policy/MANIFEST.in new file mode 100644 index 0000000..f9bd145 --- /dev/null +++ b/python-dcae-policy/MANIFEST.in @@ -0,0 +1 @@ +include requirements.txt diff --git a/python-dcae-policy/README.md b/python-dcae-policy/README.md new file mode 100644 index 0000000..eb26087 --- /dev/null +++ b/python-dcae-policy/README.md @@ -0,0 +1,307 @@ +# dcaepolicy - policy in dcae controller +- python-package to be used in cloudify plugins to maintain the policies lifecycle + +## [setup pypi connection](./nexus_pypi.md) to **nexus** repo server + +## build = register and upload to nexus repo server + +```bash +./dev_run.sh build +``` + +## upload the python package to nexus repo server + +```bash +./dev_run.sh upload +``` + +--- +# usage in plugins + +**requirements.txt** +```python +--extra-index-url https://YOUR_NEXUS_PYPI_SERVER/simple +dcaepolicy +``` + +**tasks.py** +- import + +```python +from dcaepolicy import Policies +``` + +# examples of **@operation** with **@Policies.<>** decorator + +## **dcae.nodes.policy** cloudify.interfaces.lifecycle.**create** + +- retrieve the latest policy data on dcae.nodes.policy node +```yaml + dcae.nodes.policy: + derived_from: cloudify.nodes.Root + properties: + policy_id: + description: PK to policy + type: string + default: DCAE_alex.Config_empty-policy + policy_apply_mode: + description: choice of how to apply the policy update (none|script) + type: string + default: none + interfaces: + cloudify.interfaces.lifecycle: + create: + implementation: dcae_policy_plugin.dcaepolicy.policy_get +``` + +```python +@operation +@Policies.populate_policy_on_node +def policy_get(**kwargs): + """decorate with @Policies.populate_policy_on_node on dcae.nodes.policy node to + retrieve the latest policy_body for policy_id + property and save it in runtime_properties + """ + pass +``` + +------ +## cloudify.interfaces.lifecycle.**configure** +- gather policy data into runtime_properties of policy consumer node +```yaml +cloudify.interfaces.lifecycle: + configure: + implementation: dcae_policy_plugin.dcaepolicy.node_configure + +``` + +```python + +from dcaepolicy import Policies, POLICIES +from .discovery import DiscoveryClient +from .demo_app import DemoApp + +APPLICATION_CONFIG = "application_config" +SERVICE_COMPONENT_NAME = "service_component_name" + +@operation +@Policies.gather_policies_to_node +def node_configure(**kwargs): + """decorate with @Policies.gather_policies_to_node on policy consumer node to + prepopulate runtime_properties[POLICIES] + """ + app_config = None + if APPLICATION_CONFIG in ctx.node.properties: + # dockerized blueprint puts the app config into property application_config + app_config = ctx.node.properties.get(APPLICATION_CONFIG) + else: + # CDAP components expect that in property app_config + app_config = ctx.node.properties.get("app_config") + + app_config = Policies.shallow_merge_policies_into(app_config) + ctx.instance.runtime_properties[APPLICATION_CONFIG] = app_config + ctx.logger.info("example: applied policy_configs to property app_config: {0}" \ + .format(json.dumps(app_config))) + + if SERVICE_COMPONENT_NAME in ctx.instance.runtime_properties: + ctx.logger.info("saving app_config({0}) to consul under key={1}" \ + .format(json.dumps(app_config), \ + ctx.instance.runtime_properties[SERVICE_COMPONENT_NAME])) + DiscoveryClient.put_kv(ctx.instance.runtime_properties[SERVICE_COMPONENT_NAME], app_config) + + # alternative 1 - use the list of policy configs from policies in runtime_properties + policy_configs = Policies.get_policy_configs() + if policy_configs: + ctx.logger.warn("TBD: apply policy_configs: {0}".format(json.dumps(policy_configs))) + + # alternative 2 - use the policies dict by policy_id from runtime_properties + if POLICIES in ctx.instance.runtime_properties: + policies = ctx.instance.runtime_properties[POLICIES] + ctx.logger.warn("TBD: apply policies: {0}".format(json.dumps(policies))) + + ctx.logger.info("deploying the demo component: {0}...".format(ctx.node.id)) + demo_app = DemoApp(ctx.node.id) + demo_app.start() + ctx.logger.info("deployed the demo component: {0}".format(demo_app.container_id)) + demo_app.get_logs() +``` + +------ +## execute-operation **policy-update** +```yaml +dcae.interfaces.policy: + policy_update: + implementation: dcae_policy_plugin.dcaepolicy.policy_update +``` + +execute-operation **policy-update** that gets a list of changed policy-configs +```python + +from .discovery import DiscoveryClient +from .demo_app import DemoApp + +APPLICATION_CONFIG = "application_config" +SERVICE_COMPONENT_NAME = "service_component_name" + +@operation +@Policies.update_policies_on_node(configs_only=True) +def policy_update(updated_policies, notify_app_through_script=False, **kwargs): + """decorate with @Policies.update_policies_on_node() to update runtime_properties[POLICIES] + + :updated_policies: contains the list of changed policy-configs when configs_only=True (default). + Use configs_only=False to bring the full policy objects in :updated_policies:. + + :notify_app_through_script: in kwargs is set to True/False to indicate whether to invoke + the script based on policy_apply_mode property in the blueprint + """ + + if not updated_policies or POLICIES not in ctx.instance.runtime_properties: + return + + app_config = DiscoveryClient.get_value(ctx.instance.runtime_properties[SERVICE_COMPONENT_NAME]) + app_config = Policies.shallow_merge_policies_into(app_config) + ctx.instance.runtime_properties[APPLICATION_CONFIG] = app_config + ctx.logger.info("example: updated app_config {0} with updated_policies: {1}" \ + .format(json.dumps(app_config), json.dumps(updated_policies))) + DiscoveryClient.put_kv(ctx.instance.runtime_properties[SERVICE_COMPONENT_NAME], app_config) + + if notify_app_through_script: + demo_app = DemoApp(ctx.node.id) + demo_app.notify_app_through_script( + POLICY_MESSAGE_TYPE, + updated_policies=updated_policies, + application_config=app_config + ) + + # alternative 1 - use the list of updated_policies on your own + if updated_policies: + ctx.logger.warn("TBD: apply updated_policies: {0}".format(json.dumps(updated_policies))) +``` + +example of the **changed\_policies** with **configs_only=True** +- list of config objects (preparsed from json string) +- manual mess produced by mock_policy_updater +```json +[{ + "policy_updated_from_ver": "2", + "policy_updated_to_ver": "3", + "updated_policy_id": "DCAE_alex.Config_db_client_policy_id_value", + "policy_hello": "world!", + "policy_updated_ts": "2017-08-17T21:49:39.279187Z" +}] +``` +--- + +example of **policies** in runtime_properties **before policy-update** + +```json +"runtime_properties": { + "execute_operation": "policy_update", + "service_component_name": "some-uuid.unknown.unknown.unknown.dcae.ecomp.company.com", + "application_config": { + "policy_hello": "world!", + "db": { + "type": "db", + "input_db_port": 5555, + "database_port": 5555 + }, + "policy_updated_from_ver": "1", + "intention": "policies are shallow merged to the copy of the application_config", + "updated_policy_id": "DCAE_alex.Config_db_client_policy_id_value", + "client": { + "client_version": "1.2.2", + "type": "client", + "client_policy_id": "DCAE_alex.Config_db_client_policy_id_value" + }, + "policy_updated_ts": "2017-08-17T21:13:47.268782Z", + "policy_updated_to_ver": "2" + }, + "exe_task": "node_configure", + "policies": { + "DCAE_alex.Config_db_client_policy_id_value": { + "policy_apply_mode": "script", + "policy_body": { + "policyName": "DCAE_alex.Config_db_client_policy_id_value.2.xml", + "policyConfigMessage": "Config Retrieved! ", + "responseAttributes": { + + }, + "policyConfigStatus": "CONFIG_RETRIEVED", + "matchingConditions": { + "ECOMPName": "DCAE", + "ConfigName": "alex_config_name" + }, + "type": "OTHER", + "property": null, + "config": { + "policy_updated_from_ver": "1", + "policy_updated_to_ver": "2", + "updated_policy_id": "DCAE_alex.Config_db_client_policy_id_value", + "policy_hello": "world!", + "policy_updated_ts": "2017-08-17T21:13:47.268782Z" + }, + "policyVersion": "2" + }, + "policy_id": "DCAE_alex.Config_db_client_policy_id_value" + } + } +} +``` + +example of **policies** in runtime_properties **after policy-update** + +```json +"runtime_properties": { + "execute_operation": "policy_update", + "service_component_name": "some-uuid.unknown.unknown.unknown.dcae.ecomp.company.com", + "application_config": { + "policy_hello": "world!", + "db": { + "input_db_port": 5555, + "type": "db", + "database_port": 5555 + }, + "policy_updated_ts": "2017-08-17T21:49:39.279187Z", + "policy_updated_from_ver": "2", + "intention": "policies are shallow merged to the copy of the application_config", + "client": { + "client_version": "1.2.2", + "type": "client", + "client_policy_id": "DCAE_alex.Config_db_client_policy_id_value" + }, + "updated_policy_id": "DCAE_alex.Config_db_client_policy_id_value", + "policy_updated_to_ver": "3" + }, + "exe_task": "node_configure", + "policies": { + "DCAE_alex.Config_db_client_policy_id_value": { + "policy_apply_mode": "script", + "policy_body": { + "policyName": "DCAE_alex.Config_db_client_policy_id_value.3.xml", + "policyConfigMessage": "Config Retrieved! ", + "responseAttributes": { + + }, + "policyConfigStatus": "CONFIG_RETRIEVED", + "matchingConditions": { + "ECOMPName": "DCAE", + "ConfigName": "alex_config_name" + }, + "type": "OTHER", + "property": null, + "config": { + "policy_updated_from_ver": "2", + "policy_updated_to_ver": "3", + "updated_policy_id": "DCAE_alex.Config_db_client_policy_id_value", + "policy_hello": "world!", + "policy_updated_ts": "2017-08-17T21:49:39.279187Z" + }, + "policyVersion": "3" + }, + "policy_id": "DCAE_alex.Config_db_client_policy_id_value" + } + } +} +``` + +--- \ No newline at end of file diff --git a/python-dcae-policy/dcaepolicy/__init__.py b/python-dcae-policy/dcaepolicy/__init__.py new file mode 100644 index 0000000..b3658b1 --- /dev/null +++ b/python-dcae-policy/dcaepolicy/__init__.py @@ -0,0 +1,22 @@ +"""expose the Policies class on the package level""" + +# org.onap.dcae +# ================================================================================ +# Copyright (c) 2017 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. + +from .dcae_policy import Policies, POLICIES, POLICY_MESSAGE_TYPE diff --git a/python-dcae-policy/dcaepolicy/dcae_consul_client.py b/python-dcae-policy/dcaepolicy/dcae_consul_client.py new file mode 100644 index 0000000..9856eeb --- /dev/null +++ b/python-dcae-policy/dcaepolicy/dcae_consul_client.py @@ -0,0 +1,45 @@ +"""client to talk to consul on standard port 8500""" + +# org.onap.dcae +# ================================================================================ +# Copyright (c) 2017 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. + +import requests + +CONSUL_HOST = "localhost" +CONSUL_PORT = 8500 + +class ConsulClient(object): + """talking to the local Consul agent for interfacing with Consul from the plugin. + Safe to assume that the Consul agent is always at localhost. + """ + CONSUL_SERVICE_MASK = "{0}/v1/catalog/service/{1}" + SERVICE_MASK = "http://{0}:{1}" + _consul_url = "http://{0}:{1}".format(CONSUL_HOST, CONSUL_PORT) + + @staticmethod + def get_service_url(service_name): + """find the service record in consul""" + response = requests.get(ConsulClient.CONSUL_SERVICE_MASK.format( \ + ConsulClient._consul_url, service_name)) + response.raise_for_status() + resp_json = response.json() + if resp_json: + service = resp_json[0] + return ConsulClient.SERVICE_MASK.format( \ + service["ServiceAddress"], service["ServicePort"]) diff --git a/python-dcae-policy/dcaepolicy/dcae_policy.py b/python-dcae-policy/dcaepolicy/dcae_policy.py new file mode 100644 index 0000000..1ae9b8f --- /dev/null +++ b/python-dcae-policy/dcaepolicy/dcae_policy.py @@ -0,0 +1,289 @@ +"""dcae_policy contains decorators for the policy lifecycle in cloudify""" + +# org.onap.dcae +# ================================================================================ +# Copyright (c) 2017 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. + + +import json +import uuid +import copy +from functools import wraps + +import requests + +from cloudify import ctx +from cloudify.context import NODE_INSTANCE +from cloudify.exceptions import NonRecoverableError + +from .dcae_consul_client import ConsulClient + +POLICIES = 'policies' +SERVICE_NAME_POLICY_HANDLER = "policy_handler" +X_ECOMP_REQUESTID = 'X-ECOMP-RequestID' + +POLICY_ID = 'policy_id' +POLICY_APPLY_MODE = 'policy_apply_mode' +POLICY_BODY = 'policy_body' +POLICY_VERSION = "policyVersion" +POLICY_CONFIG = 'config' +DCAE_POLICY_TYPE = 'dcae.nodes.policy' +POLICY_MESSAGE_TYPE = 'policy' +POLICY_NOTIFICATION_SCRIPT = 'script' + +class Policies(object): + """static class for policy operations""" + _policy_handler_url = None + + @staticmethod + def _get_latest_policy(policy_id): + """retrieve the latest policy for policy_id from policy-handler""" + if not Policies._policy_handler_url: + Policies._policy_handler_url = ConsulClient.get_service_url(SERVICE_NAME_POLICY_HANDLER) + + ph_path = "{0}/policy_latest/{1}".format(Policies._policy_handler_url, policy_id) + headers = {X_ECOMP_REQUESTID: str(uuid.uuid4())} + + ctx.logger.info("getting latest policy from {0} headers={1}".format( \ + ph_path, json.dumps(headers))) + res = requests.get(ph_path, headers=headers) + res.raise_for_status() + + if res.status_code == requests.codes.ok: + return res.json() + return {} + + @staticmethod + def populate_policy_on_node(func): + """dcae.nodes.policy node retrieves the policy_body identified by policy_id property + from policy-handler that gets it from policy-engine. + + Places the found policy into runtime_properties["policy_body"]. + """ + if not func: + return + + @wraps(func) + def wrapper(*args, **kwargs): + """retrieve and save the latest policy body per policy_id""" + try: + if ctx.type != NODE_INSTANCE: + return func(*args, **kwargs) + + if POLICY_ID not in ctx.node.properties: + ctx.logger.error("no {0} found in ctx.node.properties".format(POLICY_ID)) + return func(*args, **kwargs) + + policy_id = ctx.node.properties[POLICY_ID] + policy = Policies._get_latest_policy(policy_id) + if policy: + ctx.logger.info("found policy {0}".format(json.dumps(policy))) + if POLICY_BODY in policy: + ctx.instance.runtime_properties[POLICY_BODY] = policy[POLICY_BODY] + else: + error = "policy not found for policy_id {0}".format(policy_id) + ctx.logger.error(error) + raise NonRecoverableError(error) + + except Exception as ex: + error = "Failed to get the policy {0}".format(str(ex)) + ctx.logger.error(error) + raise NonRecoverableError(error) + + return func(*args, **kwargs) + return wrapper + + @staticmethod + def gather_policies_to_node(func): + """decorate with @Policies.gather_policies_to_node to + gather the policies from dcae.nodes.policy nodes this node depends on. + + Places the policies into runtime_properties["policies"]. + + Call Policies.shallow_merge_policies_into(config) to merge the policies into config. + """ + def _merge_policy_with_node(target): + """get all properties of the policy node and add the actual policy""" + policy = dict(target.node.properties) + if POLICY_BODY in target.instance.runtime_properties: + policy[POLICY_BODY] = target.instance.runtime_properties[POLICY_BODY] + return policy + + if not func: + return + + @wraps(func) + def wrapper(*args, **kwargs): + """gather and save the policies from dcae.nodes.policy nodes this node related to""" + try: + if ctx.type == NODE_INSTANCE: + policies = dict([(rel.target.node.properties[POLICY_ID], \ + _merge_policy_with_node(rel.target)) \ + for rel in ctx.instance.relationships \ + if DCAE_POLICY_TYPE in rel.target.node.type_hierarchy \ + and POLICY_ID in rel.target.node.properties \ + and rel.target.node.properties[POLICY_ID] \ + ]) + if policies: + ctx.instance.runtime_properties[POLICIES] = policies + except Exception as ex: + error = "Failed to set the policies {0}".format(str(ex)) + ctx.logger.error(error) + raise NonRecoverableError(error) + + return func(*args, **kwargs) + return wrapper + + @staticmethod + def _update_policies_on_ctx(updated_policies): + """update policies in runtime_properties and return changed_policies""" + if POLICIES not in ctx.instance.runtime_properties: + return + if not updated_policies: + ctx.logger.error("update_policies_on_ctx - no updated_policies provided in arguments") + return + + policies = ctx.instance.runtime_properties[POLICIES] + ctx.logger.info("update_policies_on_ctx: {0}".format(json.dumps(updated_policies))) + changed_policies = [] + ignored_policies = [] + unexpected_policies = [] + same_policies = [] + for policy in updated_policies: + if POLICY_ID not in policy or policy[POLICY_ID] not in policies: + ignored_policies.append(policy) + continue + if POLICY_BODY not in policy or POLICY_VERSION not in policy[POLICY_BODY] \ + or not policy[POLICY_BODY][POLICY_VERSION]: + unexpected_policies.append(policy) + continue + + deployed_policy = policies[policy[POLICY_ID]].get(POLICY_BODY, {}) + new_policy_body = policy[POLICY_BODY] + if not deployed_policy or POLICY_VERSION not in deployed_policy \ + or not deployed_policy[POLICY_VERSION] \ + or deployed_policy[POLICY_VERSION] != new_policy_body[POLICY_VERSION]: + policies[policy[POLICY_ID]][POLICY_BODY] = new_policy_body + changed_policies.append(dict(policies[policy[POLICY_ID]])) + else: + same_policies.append(policy) + + if same_policies: + ctx.logger.info("same policies: {0}".format(json.dumps(same_policies))) + if ignored_policies: + ctx.logger.info("ignored policies: {0}".format(json.dumps(ignored_policies))) + if unexpected_policies: + ctx.logger.warn("unexpected policies: {0}".format(json.dumps(unexpected_policies))) + + if changed_policies: + ctx.instance.runtime_properties[POLICIES] = policies + return changed_policies + + @staticmethod + def update_policies_on_node(configs_only=True): + """decorate each policy_update operation with @Policies.update_policies_on_node to + filter out the updated_policies to only what applies to the current node instance, + update runtime_properties["policies"] + + :configs_only: - set to True if expect to see only the config in updated_policies + instead of the whole policy object (False) + + Passes through the filtered list of updated_policies that apply to the current node instance + + :updated_policies: contains the list of changed policy-configs when configs_only=True. + + :notify_app_through_script: in kwargs is set to True/False to indicate whether to invoke + the script based on policy_apply_mode property in the blueprint + """ + def update_policies_decorator(func): + """actual decorator""" + if not func: + return + + @wraps(func) + def wrapper(updated_policies, **kwargs): + """update matching policies on context""" + if ctx.type != NODE_INSTANCE: + return + + updated_policies = Policies._update_policies_on_ctx(updated_policies) + if updated_policies: + notify_app_through_script = max( + updated_policies, + key=lambda pol: pol.get(POLICY_APPLY_MODE) == POLICY_NOTIFICATION_SCRIPT + ) + + if configs_only: + updated_policies = [policy[POLICY_BODY][POLICY_CONFIG] \ + for policy in updated_policies \ + if POLICY_BODY in policy \ + and POLICY_CONFIG in policy[POLICY_BODY] \ + ] + return func(updated_policies, + notify_app_through_script=notify_app_through_script, **kwargs) + return wrapper + return update_policies_decorator + + @staticmethod + def get_notify_app_through_script(): + """returns True if any of the policy has property policy_apply_mode==script""" + if ctx.type != NODE_INSTANCE \ + or POLICIES not in ctx.instance.runtime_properties: + return + policies = ctx.instance.runtime_properties[POLICIES] + if not policies: + return + for policy_id in policies: + if policies[policy_id].get(POLICY_APPLY_MODE) == POLICY_NOTIFICATION_SCRIPT: + return True + + @staticmethod + def get_policy_configs(): + """returns the list of policy configs from the runtime policies""" + if ctx.type != NODE_INSTANCE \ + or POLICIES not in ctx.instance.runtime_properties: + return + policies = ctx.instance.runtime_properties[POLICIES] + if not policies: + return + policy_configs = [policies[policy_id][POLICY_BODY][POLICY_CONFIG] \ + for policy_id in policies \ + if POLICY_BODY in policies[policy_id] \ + and POLICY_CONFIG in policies[policy_id][POLICY_BODY] \ + ] + return policy_configs + + @staticmethod + def shallow_merge_policies_into(config): + """shallow merge the policy configs (dict) into config that is expected to be a dict""" + if config is None: + config = {} + policy_configs = Policies.get_policy_configs() + if not policy_configs or not isinstance(config, dict): + return config + + for policy_config in copy.deepcopy(policy_configs): + if not isinstance(policy_config, dict): + continue + + config.update(policy_config) + for cfg_item in policy_config: + if policy_config[cfg_item] is None: + config.pop(cfg_item, None) + + return config diff --git a/python-dcae-policy/dev_run.sh b/python-dcae-policy/dev_run.sh new file mode 100644 index 0000000..f7cadcc --- /dev/null +++ b/python-dcae-policy/dev_run.sh @@ -0,0 +1,47 @@ +#!/bin/sh + +# org.onap.dcae +# ================================================================================ +# Copyright (c) 2017 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. + + +# nexus has to be listed in ~/.pypirc as repository and contain the url like this +# [nexus] +# repository: https://YOUR_NEXUS_PYPI_SERVER/ + +case "$1" in + register) + echo "python setup.py register -r nexus --show-response" + python setup.py register -r nexus --show-response + ;; + upload) + echo "python setup.py sdist upload -r nexus --show-response" + python setup.py sdist upload -r nexus --show-response + ;; + build) + echo "python setup.py sdist register -r nexus --show-response upload -r nexus --show-response" + python setup.py sdist register -r nexus --show-response upload -r nexus --show-response + ;; + *) + SELF=$(basename "$0") + echo "Usage:" + echo "./${SELF} register" + echo "./${SELF} upload" + echo "./${SELF} build" + ;; +esac \ No newline at end of file diff --git a/python-dcae-policy/nexus_pypi.md b/python-dcae-policy/nexus_pypi.md new file mode 100644 index 0000000..7c27679 --- /dev/null +++ b/python-dcae-policy/nexus_pypi.md @@ -0,0 +1,26 @@ +# setting up connection to nexus server with pypi + +1. request account in nexus repo server with update privilege to pypi repo. + +result: **url + username + password** + +2. create/update `~/.pypirc` with proper nexus **url + username + password** +```bash +[distutils] +index-servers = + pypi + nexus + +[pypi] +username: +password: + +[nexus] +repository:https://YOUR_NEXUS_PYPI_SERVER/ +username: +password: +``` + +3. run `./dev_run.sh register` command to register your python package into nexus pypi repo + +4. run `./dev_run.sh upload` command to upload your python package into nexus pypi repo \ No newline at end of file diff --git a/python-dcae-policy/requirements.txt b/python-dcae-policy/requirements.txt new file mode 100644 index 0000000..cdcf98d --- /dev/null +++ b/python-dcae-policy/requirements.txt @@ -0,0 +1,2 @@ +cloudify-plugins-common==3.4 +requests>=2.11.0,<3.0.0 diff --git a/python-dcae-policy/setup.py b/python-dcae-policy/setup.py new file mode 100644 index 0000000..1fd4550 --- /dev/null +++ b/python-dcae-policy/setup.py @@ -0,0 +1,41 @@ +"""setup.py is used for package build and distribution""" + +# org.onap.dcae +# ================================================================================ +# Copyright (c) 2017 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. + +from setuptools import setup + +setup( + name='dcaepolicy', + description='lib of policy decorators to be used by cloudify plugins of dcae controller', + version="0.0.2", + author='Alex Shatov', + email="dcae@lists.openecomp.org", + packages=['dcaepolicy'], + install_requires=[ + "cloudify-plugins-common==3.4", + "requests>=2.11.0,<3.0.0" + ], + keywords='policy dcae controller cloudify plugin', + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'Programming Language :: Python :: 2.7' + ] +) -- cgit 1.2.3-korg