summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoralex_sh <alexs@att.com>2017-08-24 11:44:59 -0400
committeralex_sh <alexs@att.com>2017-08-24 11:44:59 -0400
commit62fd5bed6a6f8ff8c6b159e38d7462edda0cc3b0 (patch)
treee4795c46af75bc4f0a6728eaf83845112842fb89
parent81b9ed7a3af1032caa86f316a21e9b2912bb1271 (diff)
added python-dcae-policy to utils
Change-Id: Iefba53b3fa219ff799828e5128abd7503d0fe75c Issue-Id: DCAEGEN2-80 Signed-off-by: Alex Shatov <alexs@att.com>
-rw-r--r--.gitignore70
-rw-r--r--python-dcae-policy/LICENSE.txt30
-rw-r--r--python-dcae-policy/MANIFEST.in1
-rw-r--r--python-dcae-policy/README.md307
-rw-r--r--python-dcae-policy/dcaepolicy/__init__.py22
-rw-r--r--python-dcae-policy/dcaepolicy/dcae_consul_client.py45
-rw-r--r--python-dcae-policy/dcaepolicy/dcae_policy.py289
-rw-r--r--python-dcae-policy/dev_run.sh47
-rw-r--r--python-dcae-policy/nexus_pypi.md26
-rw-r--r--python-dcae-policy/requirements.txt2
-rw-r--r--python-dcae-policy/setup.py41
11 files changed, 880 insertions, 0 deletions
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:<username>
+password:<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'
+ ]
+)