Initial commit for clamp-policy plugin 80/83480/6
authorVignesh K <vignesh.k041@wipro.com>
Wed, 27 Mar 2019 12:04:08 +0000 (17:34 +0530)
committerVignesh K <vignesh.k041@wipro.com>
Wed, 10 Apr 2019 15:12:17 +0000 (20:42 +0530)
Addressed review comments

Issue-ID: DCAEGEN2-1129

Change-Id: Icdf95bdbf7c3ec2f201d5d57544ef6d206f3c5fa
Signed-off-by: Vignesh K <vignesh.k041@wipro.com>
19 files changed:
clamp-policy/.coveragerc [new file with mode: 0644]
clamp-policy/LICENSE.txt [new file with mode: 0644]
clamp-policy/MANIFEST.in [new file with mode: 0644]
clamp-policy/README.md [new file with mode: 0644]
clamp-policy/clamppolicy-node-type.yaml [new file with mode: 0644]
clamp-policy/clamppolicyplugin/__init__.py [new file with mode: 0644]
clamp-policy/clamppolicyplugin/tasks.py [new file with mode: 0644]
clamp-policy/pom.xml [new file with mode: 0644]
clamp-policy/requirements.txt [new file with mode: 0644]
clamp-policy/setup.py [new file with mode: 0644]
clamp-policy/tests/__init__.py [new file with mode: 0644]
clamp-policy/tests/log_ctx.py [new file with mode: 0644]
clamp-policy/tests/mock_cloudify_ctx.py [new file with mode: 0644]
clamp-policy/tests/mock_setup.py [new file with mode: 0644]
clamp-policy/tests/test_tasks.py [new file with mode: 0644]
clamp-policy/tox-local.ini [new file with mode: 0644]
clamp-policy/tox.ini [new file with mode: 0644]
mvn-phase-script.sh
pom.xml

diff --git a/clamp-policy/.coveragerc b/clamp-policy/.coveragerc
new file mode 100644 (file)
index 0000000..22cdf0c
--- /dev/null
@@ -0,0 +1,25 @@
+# .coveragerc to control coverage.py
+[run]
+branch = True
+cover_pylib = False
+# include = */clamppolicyplugin/*.py
+
+[report]
+# Regexes for lines to exclude from consideration
+exclude_lines =
+    # Have to re-enable the standard pragma
+    pragma: no cover
+
+    # Don't complain about missing debug-only code:
+    def __repr__
+    if self\.debug
+
+    # Don't complain if tests don't hit defensive assertion code:
+    raise AssertionError
+    raise NotImplementedError
+
+    # Don't complain if non-runnable code isn't run:
+    if 0:
+    if __name__ == .__main__.:
+
+ignore_errors = True
diff --git a/clamp-policy/LICENSE.txt b/clamp-policy/LICENSE.txt
new file mode 100644 (file)
index 0000000..5bf12c1
--- /dev/null
@@ -0,0 +1,15 @@
+================================================================================
+Copyright (c) 2019 Wipro Limited 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=========================================================
diff --git a/clamp-policy/MANIFEST.in b/clamp-policy/MANIFEST.in
new file mode 100644 (file)
index 0000000..f9bd145
--- /dev/null
@@ -0,0 +1 @@
+include requirements.txt
diff --git a/clamp-policy/README.md b/clamp-policy/README.md
new file mode 100644 (file)
index 0000000..c26fb15
--- /dev/null
@@ -0,0 +1,27 @@
+#supporting policy_model_id for clamp
+
+ - Adding a new node type extended from dcae.node.policy, so it is backward comatible with policy_id and 
+   compatible with newly introduced policy_model_id
+
+ ## Usage
+
+import the clamppolicy-node-type.yaml into your blueprint to use the clamp.nodes.type node
+
+```yaml
+imports:
+    - https://YOUR_NEXUS_RAW_SERVER/type_files/clamppolicy/1.0.0/clamppolicy-node-type.yaml
+```
+
+provide the value for policy_model_id property
+
+```yaml
+node_templates:
+...
+  policy_model:
+    type: clamp.nodes.policy
+    properties:
+        policy_model_id: { get_input: policy_model_id }
+```
+
+Then the clamppolicyplugin will bring the latest policy model id to the clamp.nodes.policy node 
+during the install workflow of cloudify.
diff --git a/clamp-policy/clamppolicy-node-type.yaml b/clamp-policy/clamppolicy-node-type.yaml
new file mode 100644 (file)
index 0000000..feb4f20
--- /dev/null
@@ -0,0 +1,42 @@
+# ================================================================================
+# Copyright (c) 2019 Wipro Limited 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=========================================================
+#
+
+tosca_definitions_version: cloudify_dsl_1_3
+
+imports:
+    - http://www.getcloudify.org/spec/cloudify/3.4/types.yaml
+    - https://nexus.onap.org/service/local/repositories/raw/content/org.onap.dcaegen2.platform.plugins/R4/dcaepolicyplugin/2.3.0/dcaepolicyplugin_types.yaml
+
+plugins:
+  clamppolicy:
+    executor: 'central_deployment_agent'
+    package_name: clamppolicyplugin
+    package_version: 1.0.0
+
+relationships:
+  # relationship that maintains mapping between micro-service and corresponding model id
+  clamp_node.relationships.gets_input_from:
+    derived_from: cloudify.relationships.depends_on
+
+node_types:
+    # node that maintains mapping between micro-service and corresponding model id
+    clamp.nodes.policy:
+        derived_from: dcae.nodes.policy
+        properties:
+            policy_model_id:
+                description: policy-model-id generated by SDC
+                type: string
\ No newline at end of file
diff --git a/clamp-policy/clamppolicyplugin/__init__.py b/clamp-policy/clamppolicyplugin/__init__.py
new file mode 100644 (file)
index 0000000..9f32d4c
--- /dev/null
@@ -0,0 +1,20 @@
+# ================================================================================
+# Copyright (c) 2019 Wipro Limited 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=========================================================
+#
+
+""":clamppolicyplugin: gets the policy model id and performs no operation"""
+
+from .tasks import policy_get
diff --git a/clamp-policy/clamppolicyplugin/tasks.py b/clamp-policy/clamppolicyplugin/tasks.py
new file mode 100644 (file)
index 0000000..40b1659
--- /dev/null
@@ -0,0 +1,29 @@
+# ================================================================================
+# Copyright (c) 2019 Wipro Limited 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=========================================================
+#
+
+"""tasks are the cloudify operations invoked on interfaces defined in the blueprint"""
+
+from cloudify import ctx
+from cloudify.decorators import operation
+from cloudify.exceptions import NonRecoverableError
+
+CLAMP_POLICY_TYPE = 'clamp.nodes.policy'
+
+@operation
+def policy_get(**kwargs):
+    """clamppolicyplugin - Dummy Function returning no value"""
+    ctx.logger.info("clamppolicyplugin - Inside policy_get dummy function")
diff --git a/clamp-policy/pom.xml b/clamp-policy/pom.xml
new file mode 100644 (file)
index 0000000..87c2a26
--- /dev/null
@@ -0,0 +1,164 @@
+<?xml version="1.0"?>
+<!--
+================================================================================
+Copyright (c) 2019 Wipro Limited 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=========================================================
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.onap.dcaegen2.platform</groupId>
+    <artifactId>plugins</artifactId>
+    <version>1.1.0-SNAPSHOT</version>
+  </parent>
+  <groupId>org.onap.dcaegen2.platform.plugins</groupId>
+  <artifactId>clamp-policy</artifactId>
+  <name>clamp-policy-plugin</name>
+  <version>1.0.0</version>
+  <url>http://maven.apache.org</url>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <sonar.sources>.</sonar.sources>
+    <sonar.junit.reportsPath>xunit-results.xml</sonar.junit.reportsPath>
+    <sonar.python.coverage.reportPath>coverage.xml</sonar.python.coverage.reportPath>
+    <sonar.language>py</sonar.language>
+    <sonar.pluginName>Python</sonar.pluginName>
+    <sonar.inclusions>**/*.py</sonar.inclusions>
+    <sonar.exclusions>tests/*,setup.py</sonar.exclusions>
+  </properties>
+  <build>
+    <finalName>${project.artifactId}-${project.version}</finalName>
+    <plugins>
+      <!-- plugin>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <version>2.4.1</version>
+        <configuration>
+          <descriptors>
+            <descriptor>assembly/dep.xml</descriptor>
+          </descriptors>
+        </configuration>
+        <executions>
+          <execution>
+            <id>make-assembly</id>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin -->
+      <!-- now we configure custom action (calling a script) at various lifecycle phases -->
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>exec-maven-plugin</artifactId>
+        <version>1.2.1</version>
+        <executions>
+          <execution>
+            <id>clean phase script</id>
+            <phase>clean</phase>
+            <goals>
+              <goal>exec</goal>
+            </goals>
+            <configuration>
+              <arguments>
+                <argument>${project.artifactId}</argument>
+                <argument>clean</argument>
+              </arguments>
+            </configuration>
+          </execution>
+          <execution>
+            <id>generate-sources script</id>
+            <phase>generate-sources</phase>
+            <goals>
+              <goal>exec</goal>
+            </goals>
+            <configuration>
+              <arguments>
+                <argument>${project.artifactId}</argument>
+                <argument>generate-sources</argument>
+              </arguments>
+            </configuration>
+          </execution>
+          <execution>
+            <id>compile script</id>
+            <phase>compile</phase>
+            <goals>
+              <goal>exec</goal>
+            </goals>
+            <configuration>
+              <arguments>
+                <argument>${project.artifactId}</argument>
+                <argument>compile</argument>
+              </arguments>
+            </configuration>
+          </execution>
+          <execution>
+            <id>package script</id>
+            <phase>package</phase>
+            <goals>
+              <goal>exec</goal>
+            </goals>
+            <configuration>
+              <arguments>
+                <argument>${project.artifactId}</argument>
+                <argument>package</argument>
+              </arguments>
+            </configuration>
+          </execution>
+          <execution>
+            <id>test script</id>
+            <phase>test</phase>
+            <goals>
+              <goal>exec</goal>
+            </goals>
+            <configuration>
+              <arguments>
+                <argument>${project.artifactId}</argument>
+                <argument>test</argument>
+              </arguments>
+            </configuration>
+          </execution>
+          <execution>
+            <id>install script</id>
+            <phase>install</phase>
+            <goals>
+              <goal>exec</goal>
+            </goals>
+            <configuration>
+              <arguments>
+                <argument>${project.artifactId}</argument>
+                <argument>install</argument>
+              </arguments>
+            </configuration>
+          </execution>
+          <execution>
+            <id>deploy script</id>
+            <phase>deploy</phase>
+            <goals>
+              <goal>exec</goal>
+            </goals>
+            <configuration>
+              <arguments>
+                <argument>${project.artifactId}</argument>
+                <argument>deploy</argument>
+              </arguments>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/clamp-policy/requirements.txt b/clamp-policy/requirements.txt
new file mode 100644 (file)
index 0000000..025de9d
--- /dev/null
@@ -0,0 +1 @@
+requests>=2.11.0,<3.0.0
\ No newline at end of file
diff --git a/clamp-policy/setup.py b/clamp-policy/setup.py
new file mode 100644 (file)
index 0000000..de6b119
--- /dev/null
@@ -0,0 +1,37 @@
+# ================================================================================
+# Copyright (c) 2019 Wipro Limited 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=========================================================
+#
+
+"""package for clamppolicyplugin - getting policy model id from policy-engine through policy-handler"""
+
+from setuptools import setup
+
+setup(
+    name='clamppolicyplugin',
+    description='Cloudify plugin for clamp.nodes.policy node to retrieve the policy model id',
+    version="1.0.0",
+    author='Vignesh K',
+    packages=['clamppolicyplugin'],
+    install_requires=[
+        "requests>=2.11.0,<3.0.0"
+    ],
+    keywords='clamp policy model cloudify plugin',
+    classifiers=[
+        'Development Status :: 4 - Beta',
+        'Intended Audience :: Developers',
+        'Programming Language :: Python :: 2.7'
+    ]
+)
diff --git a/clamp-policy/tests/__init__.py b/clamp-policy/tests/__init__.py
new file mode 100644 (file)
index 0000000..20c17e4
--- /dev/null
@@ -0,0 +1,16 @@
+# ================================================================================
+# Copyright (c) 2019 Wipro Limited 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=========================================================
+#
diff --git a/clamp-policy/tests/log_ctx.py b/clamp-policy/tests/log_ctx.py
new file mode 100644 (file)
index 0000000..89ed9ea
--- /dev/null
@@ -0,0 +1,137 @@
+# ================================================================================
+# Copyright (c) 2019 Wipro Limited 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=========================================================
+#
+
+""":@CtxLogger.log_ctx: decorator for logging the cloudify ctx before and after operation"""
+
+import json
+import traceback
+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
+    def _get_ctx_node_info(ctx_node):
+        if not ctx_node:
+            return {}
+        return {'id': ctx_node.id, 'name': ctx_node.name, 'type': ctx_node.type,
+                'type_hierarchy': ctx_node.type_hierarchy, 'properties': ctx_node.properties}
+
+    @staticmethod
+    def _get_ctx_instance_info(ctx_instance):
+        if not ctx_instance:
+            return {}
+        return {'id' : ctx_instance.id, 'runtime_properties' : ctx_instance.runtime_properties,
+                'relationships' : CtxLogger._get_ctx_instance_relationships_info(ctx_instance)}
+
+    @staticmethod
+    def _get_ctx_instance_relationships_info(ctx_instance):
+        if not ctx_instance or not ctx_instance.relationships:
+            return []
+        return [{'target': CtxLogger._get_ctx_source_target_info(r.target), \
+                 'type':r.type, 'type_hierarchy':r.type_hierarchy} \
+                for r in ctx_instance.relationships]
+
+    @staticmethod
+    def _get_ctx_source_target_info(ctx_source_target):
+        if not ctx_source_target:
+            return {}
+        return {'node': CtxLogger._get_ctx_node_info(ctx_source_target.node),
+                'instance' : CtxLogger._get_ctx_instance_info(ctx_source_target.instance)}
+
+    @staticmethod
+    def get_ctx_info():
+        """collect the context data from ctx"""
+        context = {
+            'type': ctx.type,
+            'blueprint.id': ctx.blueprint.id,
+            'deployment.id': ctx.deployment.id,
+            'execution_id': ctx.execution_id,
+            'workflow_id': ctx.workflow_id,
+            'task_id': ctx.task_id,
+            'task_name': ctx.task_name,
+            'task_queue': ctx.task_queue,
+            'task_target': ctx.task_target,
+            'operation': {
+                'name': ctx.operation.name,
+                'retry_number': ctx.operation.retry_number,
+                'max_retries': ctx.operation.max_retries
+            },
+            'plugin': {
+                'name': ctx.plugin.name,
+                'package_name': ctx.plugin.package_name,
+                'package_version': ctx.plugin.package_version,
+                'prefix': ctx.plugin.prefix,
+                'workdir': ctx.plugin.workdir
+            }
+        }
+        if ctx.type == NODE_INSTANCE:
+            context['node'] = CtxLogger._get_ctx_node_info(ctx.node)
+            context['instance'] = CtxLogger._get_ctx_instance_info(ctx.instance)
+        elif ctx.type == RELATIONSHIP_INSTANCE:
+            context['source'] = CtxLogger._get_ctx_source_target_info(ctx.source)
+            context['target'] = CtxLogger._get_ctx_source_target_info(ctx.target)
+
+        return context
+
+    @staticmethod
+    def log_ctx_info(func_name):
+        ctx.logger.info("NODE_INSTANCE: {}",NODE_INSTANCE)
+        """shortcut for logging of the ctx of the function"""
+        try:
+            ctx.logger.info("NODE_INSTANCE: {}",NODE_INSTANCE)
+            if ctx.type == NODE_INSTANCE:
+                ctx.logger.info("{0} {1} context: {2}".format(\
+                    func_name, ctx.instance.id, json.dumps(CtxLogger.get_ctx_info())))
+            elif ctx.type == RELATIONSHIP_INSTANCE:
+                ctx.logger.info("{0} context: {1}".format(\
+                    func_name, json.dumps(CtxLogger.get_ctx_info())))
+        except Exception as ex:
+            ctx.logger.error("Failed to log the node context: {0}: {1}" \
+                .format(str(ex), traceback.format_exc()))
+
+    @staticmethod
+    def log_ctx(pre_log=True, after_log=False, exe_task=None):
+        """Decorate each operation on the node to log the context - before and after.
+        Optionally save the current function name into runtime_properties[exe_task]
+        """
+        def log_ctx_info_decorator(func, **arguments):
+            """Decorate each operation on the node to log the context"""
+            if func is not None:
+                @wraps(func)
+                def wrapper(*args, **kwargs):
+                    """the actual logger before and after"""
+                    try:
+                        if ctx.type == NODE_INSTANCE and exe_task:
+                            ctx.instance.runtime_properties[exe_task] = func.__name__
+                    except Exception as ex:
+                        ctx.logger.error("Failed to set exe_task {0}: {1}: {2}" \
+                            .format(exe_task, str(ex), traceback.format_exc()))
+                    if pre_log:
+                        CtxLogger.log_ctx_info('before ' + func.__name__)
+
+                    result = func(*args, **kwargs)
+
+                    if after_log:
+                        CtxLogger.log_ctx_info('after ' + func.__name__)
+
+                    return result
+                return wrapper
+        return log_ctx_info_decorator
diff --git a/clamp-policy/tests/mock_cloudify_ctx.py b/clamp-policy/tests/mock_cloudify_ctx.py
new file mode 100644 (file)
index 0000000..cf40232
--- /dev/null
@@ -0,0 +1,145 @@
+# ================================================================================
+# Copyright (c) 2019 Wipro Limited 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=========================================================
+#
+
+"""mock cloudify context with relationships and type_hierarchy"""
+
+from cloudify.mocks import (MockCloudifyContext, MockNodeContext,
+                            MockNodeInstanceContext)
+
+TARGET_NODE_ID = "target_node_id"
+TARGET_NODE_NAME = "target_node_name"
+
+class MockContextNode(MockNodeContext):
+    """ctx.node with type and type_hierarchy"""
+
+    def __init__(self, id=None, properties=None, node_type=None, type_hierarchy=None):
+        super(MockContextNode, self).__init__(id, properties or {})
+        self._type = node_type
+        self._type_hierarchy = type_hierarchy or [self._type]
+        MockCloudifyContextFull.nodes[id] = self
+
+    @property
+    def type(self):
+        """node type"""
+        return self._type
+
+    @property
+    def type_hierarchy(self):
+        """node type hierarchy is a list of types"""
+        return self._type_hierarchy
+
+class MockContextNodeInstance(MockNodeInstanceContext):
+    """ctx.instance with relationships"""
+
+    def __init__(self, id=None, runtime_properties=None, relationships=None):
+        super(MockContextNodeInstance, self).__init__(id, runtime_properties or {})
+        self._relationships = []
+        self.add_relationships(relationships)
+        MockCloudifyContextFull.instances[id] = self
+
+    def add_relationships(self, relationships):
+        """add more relationships to the node instance"""
+        if not relationships:
+            return
+        if not self._relationships:
+            self._relationships = []
+        self._relationships.extend([
+            MockContextRelationship(relationship)
+            for relationship in (relationships or []) if TARGET_NODE_ID in relationship
+        ])
+
+    @property
+    def relationships(self):
+        """list of relationships to other node instances"""
+        return self._relationships
+
+class MockContextRelationshipTarget(object):
+    """target of relationship"""
+    def __init__(self, relationship):
+        target_node_name = relationship[TARGET_NODE_NAME]
+        target_node_id = relationship[TARGET_NODE_ID]
+
+        self.node = MockCloudifyContextFull.nodes.get(target_node_name)
+        self.instance = MockCloudifyContextFull.instances.get(target_node_id)
+
+        if not self.node:
+            self.node = MockContextNode(target_node_name)
+        if not self.instance:
+            self.instance = MockContextNodeInstance(target_node_id)
+
+class MockContextRelationship(object):
+    """item of ctx.instance.relationships"""
+
+    def __init__(self, relationship):
+        self.target = MockContextRelationshipTarget(relationship)
+        self.type = relationship.get("type", "cloudify.relationships.depends_on")
+        self.type_hierarchy = relationship.get("type_hierarchy") or [self.type]
+
+class MockCloudifyContextFull(MockCloudifyContext):
+    """
+    ctx1 = MockCloudifyContextFull(node_id='node_1',
+                                   node_name='my_1', properties={'foo': 'bar'})
+    ctx2 = MockCloudifyContextFull(node_id='node_2',
+                                   node_name='my_2',
+                                   relationships=[{'target_node_id': 'node_1',
+                                                   'target_node_name': 'my_1'}])
+    """
+    nodes = {}
+    instances = {}
+
+    def __init__(self,
+                 node_id=None,
+                 node_name=None,
+                 blueprint_id=None,
+                 deployment_id=None,
+                 execution_id=None,
+                 properties=None, node_type=None, type_hierarchy=None,
+                 runtime_properties=None,
+                 capabilities=None,
+                 related=None,
+                 source=None,
+                 target=None,
+                 operation=None,
+                 resources=None,
+                 provider_context=None,
+                 bootstrap_context=None,
+                 relationships=None):
+        super(MockCloudifyContextFull, self).__init__(
+            node_id=node_id,
+            node_name=node_name,
+            blueprint_id=blueprint_id,
+            deployment_id=deployment_id,
+            execution_id=execution_id,
+            properties=properties,
+            capabilities=capabilities,
+            related=related,
+            source=source,
+            target=target,
+            operation=operation,
+            resources=resources,
+            provider_context=provider_context,
+            bootstrap_context=bootstrap_context,
+            runtime_properties=runtime_properties
+        )
+        self._node = MockContextNode(node_name, properties, node_type, type_hierarchy)
+        self._instance = MockContextNodeInstance(node_id, runtime_properties, relationships)
+
+    @staticmethod
+    def clear():
+        """clean up the context links"""
+        MockCloudifyContextFull.instances.clear()
+        MockCloudifyContextFull.nodes.clear()
diff --git a/clamp-policy/tests/mock_setup.py b/clamp-policy/tests/mock_setup.py
new file mode 100644 (file)
index 0000000..147ba43
--- /dev/null
@@ -0,0 +1,155 @@
+# ================================================================================
+# Copyright (c) 2019 Wipro Limited 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=========================================================
+#
+
+"""unit tests for tasks in clamppolicyplugin"""
+
+import json
+import logging
+from datetime import datetime, timedelta
+
+from tests.mock_cloudify_ctx import MockCloudifyContextFull
+
+LOG_FILE = 'logs/test_clamppolicyplugin.log'
+POLICY_MODEL_ID = 'policy_model_id'
+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: "VigneshK_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_clamp_policy_bp_id'
+    DEPLOYMENT_ID = 'test_clamp_policy_dpl_id'
+    EXECUTION_ID = 'test_clamp_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/clamp-policy/tests/test_tasks.py b/clamp-policy/tests/test_tasks.py
new file mode 100644 (file)
index 0000000..f4c8d5a
--- /dev/null
@@ -0,0 +1,46 @@
+# ================================================================================
+# Copyright (c) 2019 Wipro Limited 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=========================================================
+#
+
+"""unit tests for tasks in clamppolicyplugin"""
+
+import json
+
+import pytest
+from cloudify.exceptions import NonRecoverableError
+from cloudify.state import current_ctx
+
+from clamppolicyplugin 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_MODEL_ID, POLICY_NAME, MonkeyedNode,
+                              MonkeyedPolicyBody, MonkeyedResponse)
+
+from cloudify.mocks import MockCloudifyContext
+from cloudify.state import current_ctx
+
+def monkeyed_policy_handler_get(full_path, headers=None):
+    """monkeypatch for the GET to policy-engine"""
+    return MonkeyedResponse(full_path, headers,
+        MonkeyedPolicyBody.create_policy(MONKEYED_POLICY_ID))
+
+def test_policy_get():
+    """test policy_get operation on clamp.nodes.policy node"""
+    mock_ctx = MockCloudifyContext(node_id='policy_model_id',node_name='clamp.nodes.policy')
+    current_ctx.set(mock_ctx)
+    tasks.policy_get()
\ No newline at end of file
diff --git a/clamp-policy/tox-local.ini b/clamp-policy/tox-local.ini
new file mode 100644 (file)
index 0000000..ad84024
--- /dev/null
@@ -0,0 +1,15 @@
+# tox -c tox-local.ini | tee -a logs/test_clamppolicyplugin.log 2>&1
+[tox]
+envlist = py27
+
+[testenv]
+deps=
+    -rrequirements.txt
+    cloudify-plugins-common==3.4
+    pytest
+    coverage
+    pytest-cov
+setenv =
+    PYTHONPATH={toxinidir}
+# recreate = True
+commands=pytest -v --cov clamppolicyplugin --cov-report html
diff --git a/clamp-policy/tox.ini b/clamp-policy/tox.ini
new file mode 100644 (file)
index 0000000..3974a7e
--- /dev/null
@@ -0,0 +1,17 @@
+# content of: tox.ini , put in same dir as setup.py
+[tox]
+envlist = py27
+
+[testenv]
+deps=
+    -rrequirements.txt
+    cloudify-plugins-common==3.4
+    pytest
+    coverage
+    pytest-cov
+setenv =
+    PYTHONPATH={toxinidir}
+commands=
+  -mkdir logs
+  pytest --junitxml xunit-results.xml --cov clamppolicyplugin --cov-report xml
+  coverage xml
index 234f73a..e404a08 100755 (executable)
@@ -63,7 +63,7 @@ test)
 package)
   echo "==> package phase script"
   case $MVN_PROJECT_MODULEID in
-  cdap|dcae-policy|docker|relationships|k8s)
+  cdap|dcae-policy|docker|relationships|k8s|clamp-policy)
     build_archives_for_wagons
     build_wagons
     ;;
@@ -77,7 +77,7 @@ install)
 deploy)
   echo "==> deploy phase script"
   case $MVN_PROJECT_MODULEID in
-  cdap|dcae-policy|docker|relationships|k8s)
+  cdap|dcae-policy|docker|relationships|k8s|clamp-policy)
     upload_wagons_and_type_yamls
     upload_wagon_archives
     ;;
diff --git a/pom.xml b/pom.xml
index 266bdc1..888331f 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -37,6 +37,7 @@ ECOMP is a trademark and service mark of AT&T Intellectual Property.
      <module>relationships</module>
      <module>dcae-policy</module>
      <module>k8s</module>
+     <module>clamp-policy</module>
   </modules>
   <properties>
     <onap.nexus.url>https://nexus.onap.org</onap.nexus.url>