Add declarative acceptance tests 30/92530/6
authorEliezio Oliveira <eliezio.oliveira@est.tech>
Wed, 31 Jul 2019 10:50:26 +0000 (11:50 +0100)
committerDan Timoney <dtimoney@att.com>
Fri, 9 Aug 2019 19:46:17 +0000 (19:46 +0000)
First two UATs are for blueprints Echo and "PNF Configuration".

The body of the ODL mount request was changed from XML to JSON,
so it can be represented in a YAML file.

Initial documentation about the UATs can be found at
components/model-catalog/blueprint-model/uat-blueprints/README.md

BluePrintArchiveUtils.recurseFiles() replaced by compressFolder() that
uses native Java 7. Removed commons-compress as dependency since is no
longer used.

Change-Id: I96a584ae12ca009f90fe8fe9485eb57ce05e8add
Issue-ID: CCSDK-1569
Signed-off-by: Eliezio Oliveira <eliezio.oliveira@est.tech>
48 files changed:
components/model-catalog/blueprint-model/test-blueprint/capability_restconf/Templates/config-deploy-restconf-mount-template.vtl [deleted file]
components/model-catalog/blueprint-model/uat-blueprints/README.md [new file with mode: 0644]
components/model-catalog/blueprint-model/uat-blueprints/echo/Definitions/artifact_types.json [new file with mode: 0644]
components/model-catalog/blueprint-model/uat-blueprints/echo/Definitions/data_types.json [new file with mode: 0644]
components/model-catalog/blueprint-model/uat-blueprints/echo/Definitions/echo-mapping.json [new file with mode: 0644]
components/model-catalog/blueprint-model/uat-blueprints/echo/Definitions/echo-test.json [new file with mode: 0644]
components/model-catalog/blueprint-model/uat-blueprints/echo/Definitions/node_types.json [new file with mode: 0644]
components/model-catalog/blueprint-model/uat-blueprints/echo/Definitions/policy_types.json [moved from components/model-catalog/blueprint-model/test-blueprint/capability_restconf/Definitions/policy_types.json with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/echo/Definitions/relationship_types.json [moved from components/model-catalog/blueprint-model/test-blueprint/capability_restconf/Definitions/relationship_types.json with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/echo/Definitions/resources_definition_types.json [new file with mode: 0644]
components/model-catalog/blueprint-model/uat-blueprints/echo/Plans/TEST_echo.xml [new file with mode: 0644]
components/model-catalog/blueprint-model/uat-blueprints/echo/TOSCA-Metadata/TOSCA.meta [new file with mode: 0644]
components/model-catalog/blueprint-model/uat-blueprints/echo/Templates/echo-template.vtl [new file with mode: 0644]
components/model-catalog/blueprint-model/uat-blueprints/echo/Tests/uat.yaml [new file with mode: 0644]
components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Definitions/activation-blueprint.json [moved from components/model-catalog/blueprint-model/test-blueprint/capability_restconf/Definitions/activation-blueprint.json with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Definitions/artifact_types.json [moved from components/model-catalog/blueprint-model/test-blueprint/capability_restconf/Definitions/artifact_types.json with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Definitions/config-assign-pnf-mapping.json [moved from components/model-catalog/blueprint-model/test-blueprint/capability_restconf/Definitions/config-assign-pnf-mapping.json with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Definitions/config-deploy-pnf-mapping.json [moved from components/model-catalog/blueprint-model/test-blueprint/capability_restconf/Definitions/config-deploy-pnf-mapping.json with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Definitions/data_types.json [moved from components/model-catalog/blueprint-model/test-blueprint/capability_restconf/Definitions/data_types.json with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Definitions/node_types.json [moved from components/model-catalog/blueprint-model/test-blueprint/capability_restconf/Definitions/node_types.json with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Definitions/policy_types.json [new file with mode: 0644]
components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Definitions/relationship_types.json [new file with mode: 0644]
components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Definitions/resources_definition_types.json [moved from components/model-catalog/blueprint-model/test-blueprint/capability_restconf/Definitions/resources_definition_types.json with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Plans/CONFIG_configAssign.xml [moved from components/model-catalog/blueprint-model/test-blueprint/capability_restconf/Plans/CONFIG_configAssign.xml with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Plans/CONFIG_configDeploy.xml [moved from components/model-catalog/blueprint-model/test-blueprint/capability_restconf/Plans/CONFIG_configDeploy.xml with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Scripts/python/RestconfConfigDeploy.py [moved from components/model-catalog/blueprint-model/test-blueprint/capability_restconf/Scripts/python/RestconfConfigDeploy.py with 98% similarity]
components/model-catalog/blueprint-model/uat-blueprints/pnf_config/TOSCA-Metadata/TOSCA.meta [moved from components/model-catalog/blueprint-model/test-blueprint/capability_restconf/TOSCA-Metadata/TOSCA.meta with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Templates/config-assign-restconf-configlet-template.vtl [moved from components/model-catalog/blueprint-model/test-blueprint/capability_restconf/Templates/config-assign-restconf-configlet-template.vtl with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Templates/config-deploy-restconf-mount-template.vtl [new file with mode: 0644]
components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Tests/uat.yaml [new file with mode: 0644]
components/parent/pom.xml
components/scripts/python/ccsdk_restconf/restconf_client.py
ms/blueprintsprocessor/application/pom.xml
ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintsAcceptanceTests.kt [new file with mode: 0644]
ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/CollectionUtils2.kt [new file with mode: 0644]
ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/ExtendedTemporaryFolder.kt [new file with mode: 0644]
ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/TestSecuritySettings.kt [new file with mode: 0644]
ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/WorkingFoldersInitializer.kt [new file with mode: 0644]
ms/blueprintsprocessor/application/src/test/resources/application-test.properties [new file with mode: 0644]
ms/blueprintsprocessor/application/src/test/resources/logback-test.xml
ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BluePrintRestLibPropertyService.kt
ms/blueprintsprocessor/parent/pom.xml
ms/controllerblueprints/modules/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/FileExtensionFunctions.kt
ms/controllerblueprints/modules/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/utils/BluePrintArchiveUtils.kt
ms/controllerblueprints/modules/db-resources/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/db/resources/BlueprintCatalogServiceImpl.kt
ms/controllerblueprints/modules/service/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/service/utils/BluePrintEnhancerUtils.kt
ms/controllerblueprints/modules/service/src/test/kotlin/org/onap/ccsdk/cds/controllerblueprints/service/enhancer/BluePrintEnhancerServiceImplTest.kt
ms/controllerblueprints/parent/pom.xml

diff --git a/components/model-catalog/blueprint-model/test-blueprint/capability_restconf/Templates/config-deploy-restconf-mount-template.vtl b/components/model-catalog/blueprint-model/test-blueprint/capability_restconf/Templates/config-deploy-restconf-mount-template.vtl
deleted file mode 100644 (file)
index ad03321..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<node xmlns="urn:TBD:params:xml:ns:yang:network-topology">
-    <node-id>$pnf-id</node-id>
-    <key-based xmlns="urn:opendaylight:netconf-node-topology">
-        <key-id xmlns="urn:opendaylight:netconf-node-topology">ODL_private_key_0</key-id>
-        <username xmlns="urn:opendaylight:netconf-node-topology">netconf</username>
-     </key-based>
-    <host xmlns="urn:opendaylight:netconf-node-topology">$pnf-ipv4-address</host>
-    <port xmlns="urn:opendaylight:netconf-node-topology">6513</port>
-    <tcp-only xmlns="urn:opendaylight:netconf-node-topology">false</tcp-only>
-    <protocol xmlns="urn:opendaylight:netconf-node-topology">
-        <name xmlns="urn:opendaylight:netconf-node-topology">TLS</name>
-    </protocol>
-    <max-connection-attempts xmlns="urn:opendaylight:netconf-node-topology">5</max-connection-attempts>
-</node>
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/README.md b/components/model-catalog/blueprint-model/uat-blueprints/README.md
new file mode 100644 (file)
index 0000000..d6a3352
--- /dev/null
@@ -0,0 +1,146 @@
+# Acceptance Testing Blueprints
+
+## What is BP User Acceptance Tests (UATs)?
+
+UATs aims to fully test the BlueprintsProcessor (BPP) using a blueprint.
+The BPP runs in an almost production-like configuration with some minor exceptions:
+
+- It uses an embedded, in-memory, and initially empty H2 database, running in MySQL/MariaDB compatibility mode;
+- All external services are mocked.
+  
+## How it works?
+
+The UATs are declarative, data-driven tests implemented in YAML 1.1 documents.
+This YAML files express:
+
+- Sequence of requests to be sent to the BPP for every process;
+- The expected BPP responses;
+- For every used external service:
+  - The `selector` used internally to instantiate the rest client;
+  - A variable set of expected requests and corresponding responses.
+
+The UAT engine will perform the following validations:
+
+- The BPP responses;
+- The payloads in the external services requests and it's content type.
+
+## Adding your BP to the suite of UATs
+
+To add a new BP to the UAT suite, all you need to do is:
+1. Add your blueprint folder under
+CDS project's `components/model-catalog/blueprint-model/uat-blueprints` directory;
+2. Create a `Tests/uat.yaml` document under your BP folder.
+
+## `uat.yaml` reference
+
+### Skeleton of a basic `uat.yaml`
+
+```yaml
+%YAML 1.1
+---
+processes:
+  - name: process1
+    request:
+      commonHeader: &commonHeader
+        originatorId: sdnc
+        requestId: "123456-1000"
+        subRequestId: sub-123456-1000
+      actionIdentifiers: &assign-ai
+        blueprintName: configuration_over_restconf
+        blueprintVersion: "1.0.0"
+        actionName: config-assign
+        mode: sync
+      payload:
+        # ...
+    expectedResponse:
+      commonHeader: *commonHeader
+      actionIdentifiers: *assign-ai
+      status:
+        code: 200
+        eventType: EVENT_COMPONENT_EXECUTED
+        errorMessage: null
+        message: success
+      payload:
+        # ...
+      stepData:
+        name: config-assign
+        properties:
+          resource-assignment-params:
+            # ...
+          status: success
+  - name: process2
+    # ...
+
+external-services:
+  - selector: odl
+    expectations:
+      - request:
+          method: GET
+          path:
+        response:
+          status: 200  # optional, 200 is the default value
+          body: # optional, default is an empty content
+            # ...
+      - request:
+          method: POST
+          path:
+          content-type: application/json
+          body:
+            # JSON request body
+        response:
+          status: 201
+```
+
+### Composite URI paths
+
+In case your YAML document contains many URI path definitions, you'd better keep the duplications
+as low as possible in order to ease the document maintenance, and avoid inconsistencies.
+Since YAML doesn't provide a standard mechanism to concatenate strings,
+the UAT engine implements an ad-hoc mechanism based on multi-level lists.
+Please note that currently this mechanism is only applied to URI paths.
+
+To exemplify how it works, let's take the case of eliminating duplications when defining multiple OpenDaylight URLs.
+
+You might starting using the following definitions:
+```yaml
+   nodeId: &nodeId "new-netconf-device"
+   # ...
+   - request:
+     path: &configUri [restconf/config, &nodeIdentifier [network-topology:network-topology/topology/topology-netconf/node, *nodeId]]
+   # ...
+   - request:
+     path: [restconf/operational, *nodeIdentifier]
+   # ...
+   - request:
+     path: [*configUri, &configletResourcePath yang-ext:mount/mynetconf:netconflist]
+```
+
+The UAT engine will expand the above multi-level lists, resulting on the following URI paths:
+```yaml
+   # ...
+   - request:
+     path: restconf/config/network-topology:network-topology/topology/topology-netconf/node/new-netconf-device
+   # ...
+   - request:
+     path: restconf/operational/network-topology:network-topology/topology/topology-netconf/node/new-netconf-device
+   # ...
+   - request:
+     path: restconf/config/network-topology:network-topology/topology/topology-netconf/node/new-netconf-device/yang-ext:mount/mynetconf:netconflist
+``` 
+
+## License
+
+Copyright (C) 2019 Nordix Foundation.
+
+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.
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/echo/Definitions/artifact_types.json b/components/model-catalog/blueprint-model/uat-blueprints/echo/Definitions/artifact_types.json
new file mode 100644 (file)
index 0000000..6ec3b41
--- /dev/null
@@ -0,0 +1,22 @@
+{
+  "artifact_types" : {
+    "artifact-directed-graph" : {
+      "description" : "Directed Graph File",
+      "version" : "1.0.0",
+      "derived_from" : "tosca.artifacts.Implementation",
+      "file_ext" : [ "json", "xml" ]
+    },
+    "artifact-mapping-resource" : {
+      "description" : "Resource Mapping File used along with Configuration template",
+      "version" : "1.0.0",
+      "derived_from" : "tosca.artifacts.Implementation",
+      "file_ext" : [ "json" ]
+    },
+    "artifact-template-velocity" : {
+      "description" : " Velocity Template used for Configuration",
+      "version" : "1.0.0",
+      "derived_from" : "tosca.artifacts.Implementation",
+      "file_ext" : [ "vtl" ]
+    }
+  }
+}
\ No newline at end of file
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/echo/Definitions/data_types.json b/components/model-catalog/blueprint-model/uat-blueprints/echo/Definitions/data_types.json
new file mode 100644 (file)
index 0000000..24f5019
--- /dev/null
@@ -0,0 +1,14 @@
+{
+  "data_types" : {
+    "dt-echo-properties" : {
+      "description" : "Dynamic DataType definition for workflow(echo).",
+      "version" : "1.0.0",
+      "properties" : {
+        "echoed-message" : {
+          "type" : "string"
+        }
+      },
+      "derived_from" : "tosca.datatypes.Dynamic"
+    }
+  }
+}
\ No newline at end of file
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/echo/Definitions/echo-mapping.json b/components/model-catalog/blueprint-model/uat-blueprints/echo/Definitions/echo-mapping.json
new file mode 100644 (file)
index 0000000..02f2b49
--- /dev/null
@@ -0,0 +1,13 @@
+[
+  {
+    "name": "echoed-message",
+    "input-param": true,
+    "property": {
+      "type": "string"
+    },
+    "dictionary-name": "echoed-message",
+    "dictionary-source": "input",
+    "dependencies": [
+    ]
+  }
+]
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/echo/Definitions/echo-test.json b/components/model-catalog/blueprint-model/uat-blueprints/echo/Definitions/echo-test.json
new file mode 100644 (file)
index 0000000..3105484
--- /dev/null
@@ -0,0 +1,91 @@
+{
+  "tosca_definitions_version" : "controller_blueprint_1_0_0",
+  "metadata" : {
+    "template_author" : "Rodrigo Ottero",
+    "author-email" : "rodrigo.ottero@est.tech",
+    "user-groups" : "ADMIN, OPERATION",
+    "template_name" : "echo_test",
+    "template_version" : "1.0.0",
+    "template_tags" : "echo_test, echo, test, testing"
+  },
+  "imports" : [ {
+    "file" : "Definitions/data_types.json"
+  }, {
+    "file" : "Definitions/relationship_types.json"
+  }, {
+    "file" : "Definitions/artifact_types.json"
+  }, {
+    "file" : "Definitions/node_types.json"
+  }, {
+    "file" : "Definitions/policy_types.json"
+  } ],
+  "topology_template" : {
+    "workflows" : {
+      "echo" : {
+        "steps" : {
+          "activate-process" : {
+            "description" : "Echo a message",
+            "target" : "echo-process",
+            "activities" : [ {
+              "call_operation" : ""
+            } ]
+          }
+        },
+        "inputs" : {
+          "echo-properties" : {
+            "description" : "Dynamic PropertyDefinition for workflow(echo).",
+            "required" : true,
+            "type" : "dt-echo-properties"
+          }
+        }
+      }
+    },
+    "node_templates" : {
+      "echo-process" : {
+        "type" : "dg-generic",
+        "properties" : {
+          "content" : {
+            "get_artifact" : [ "SELF", "dg-echo-process" ]
+          },
+          "dependency-node-templates" : [ "echo" ]
+        },
+        "artifacts" : {
+          "dg-config-assign-process" : {
+            "type" : "artifact-directed-graph",
+            "file" : "Plans/TEST_echo.xml"
+          }
+        }
+      },
+      "echo" : {
+        "type" : "component-resource-resolution",
+        "interfaces" : {
+          "ResourceResolutionComponent" : {
+            "operations" : {
+              "process" : {
+                "inputs" : {
+                  "artifact-prefix-names" : [ "echo" ]
+                },
+                "outputs" : {
+                  "resource-assignment-params" : {
+                    "get_attribute" : [ "SELF", "assignment-params" ]
+                  },
+                  "status" : "success"
+                }
+              }
+            }
+          }
+        },
+        "artifacts" : {
+          "echo-template" : {
+            "type" : "artifact-template-velocity",
+            "file" : "Templates/echo-template.vtl"
+          },
+          "echo-mapping" : {
+            "type" : "artifact-mapping-resource",
+            "file" : "Definitions/echo-mapping.json"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/echo/Definitions/node_types.json b/components/model-catalog/blueprint-model/uat-blueprints/echo/Definitions/node_types.json
new file mode 100644 (file)
index 0000000..a3fc254
--- /dev/null
@@ -0,0 +1,129 @@
+{
+  "node_types" : {
+    "component-resource-resolution" : {
+      "description" : "This is Resource Assignment Component API",
+      "version" : "1.0.0",
+      "attributes" : {
+        "assignment-params" : {
+          "required" : true,
+          "type" : "string"
+        }
+      },
+      "capabilities" : {
+        "component-node" : {
+          "type" : "tosca.capabilities.Node"
+        }
+      },
+      "interfaces" : {
+        "ResourceResolutionComponent" : {
+          "operations" : {
+            "process" : {
+              "inputs" : {
+                "resolution-key" : {
+                  "description" : "Key for service instance related correlation.",
+                  "required" : false,
+                  "type" : "string"
+                },
+                "occurrence": {
+                  "description": "Number of time to perform the resolution.",
+                  "required": false,
+                  "default": 1,
+                  "type": "integer"
+                },
+                "store-result" : {
+                  "description" : "Whether or not to store the output.",
+                  "required" : false,
+                  "type" : "boolean"
+                },
+                "resource-type" : {
+                  "description" : "Request type.",
+                  "required" : false,
+                  "type" : "string"
+                },
+                "artifact-prefix-names" : {
+                  "description" : "Template , Resource Assignment Artifact Prefix names",
+                  "required" : true,
+                  "type" : "list",
+                  "entry_schema" : {
+                    "type" : "string"
+                  }
+                },
+                "request-id" : {
+                  "description" : "Request Id, Unique Id for the request.",
+                  "required" : true,
+                  "type" : "string"
+                },
+                "resource-id" : {
+                  "description" : "Resource Id.",
+                  "required" : false,
+                  "type" : "string"
+                },
+                "action-name" : {
+                  "description" : "Action Name of the process",
+                  "required" : false,
+                  "type" : "string"
+                },
+                "dynamic-properties" : {
+                  "description" : "Dynamic Json Content or DSL Json reference.",
+                  "required" : false,
+                  "type" : "json"
+                }
+              },
+              "outputs" : {
+                "resource-assignment-params" : {
+                  "required" : true,
+                  "type" : "string"
+                },
+                "status" : {
+                  "required" : true,
+                  "type" : "string"
+                }
+              }
+            }
+          }
+        }
+      },
+      "derived_from" : "tosca.nodes.Component"
+    },
+    "dg-generic" : {
+      "description" : "This is Generic Directed Graph Type",
+      "version" : "1.0.0",
+      "properties" : {
+        "content" : {
+          "required" : true,
+          "type" : "string"
+        },
+        "dependency-node-templates" : {
+          "description" : "Dependent Step Components NodeTemplate name.",
+          "required" : true,
+          "type" : "list",
+          "entry_schema" : {
+            "type" : "string"
+          }
+        }
+      },
+      "derived_from" : "tosca.nodes.Workflow"
+    },
+    "source-input" : {
+      "description" : "This is Input Resource Source Node Type",
+      "version" : "1.0.0",
+      "properties" : { },
+      "derived_from" : "tosca.nodes.ResourceSource"
+    },
+    "tosca.nodes.Component" : {
+      "description" : "This is default Component Node",
+      "version" : "1.0.0",
+      "derived_from" : "tosca.nodes.Root"
+    },
+    "tosca.nodes.ResourceSource" : {
+      "description" : "TOSCA base type for Resource Sources",
+      "version" : "1.0.0",
+      "derived_from" : "tosca.nodes.Root"
+    },
+    "tosca.nodes.Workflow" : {
+      "description" : "This is Directed Graph Node Type",
+      "version" : "1.0.0",
+      "derived_from" : "tosca.nodes.Root"
+    }
+  }
+}
\ No newline at end of file
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/echo/Definitions/resources_definition_types.json b/components/model-catalog/blueprint-model/uat-blueprints/echo/Definitions/resources_definition_types.json
new file mode 100644 (file)
index 0000000..4b0cf47
--- /dev/null
@@ -0,0 +1,17 @@
+{
+  "echoed-message" : {
+    "tags" : "echoed-message",
+    "name" : "echoed-message",
+    "property" : {
+      "description" : "echoed-message",
+      "type" : "string"
+    },
+    "updated-by" : "Rodrigo Ottero <rodrigo.ottero@est.tech>",
+    "sources" : {
+      "input" : {
+        "type" : "source-input",
+        "properties" : { }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/echo/Plans/TEST_echo.xml b/components/model-catalog/blueprint-model/uat-blueprints/echo/Plans/TEST_echo.xml
new file mode 100644 (file)
index 0000000..4305c7d
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<service-logic xmlns="http://www.onap.org/sdnc/svclogic" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.onap.org/sdnc/svclogic ./svclogic.xsd" module="CONFIG" version="1.0.0">
+   <method rpc="ResourceAssignAndActivate" mode="sync">
+      <block atomic="true">
+         <execute plugin="echo" method="process">
+            <outcome value="failure">
+               <return status="failure" />
+            </outcome>
+            <outcome value="success">
+               <return status="success" />
+            </outcome>
+         </execute>
+      </block>
+   </method>
+</service-logic>
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/echo/TOSCA-Metadata/TOSCA.meta b/components/model-catalog/blueprint-model/uat-blueprints/echo/TOSCA-Metadata/TOSCA.meta
new file mode 100644 (file)
index 0000000..769d464
--- /dev/null
@@ -0,0 +1,5 @@
+TOSCA-Meta-File-Version: 1.0.0
+CSAR-Version: 1.0
+Created-By: Rodrigo Ottero
+Entry-Definitions: Definitions/echo-test.json
+Template-Tags: activation-blueprint
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/echo/Templates/echo-template.vtl b/components/model-catalog/blueprint-model/uat-blueprints/echo/Templates/echo-template.vtl
new file mode 100644 (file)
index 0000000..9e2dcc1
--- /dev/null
@@ -0,0 +1 @@
+${echoed-message}
\ No newline at end of file
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/echo/Tests/uat.yaml b/components/model-catalog/blueprint-model/uat-blueprints/echo/Tests/uat.yaml
new file mode 100644 (file)
index 0000000..1162309
--- /dev/null
@@ -0,0 +1,34 @@
+%YAML 1.1
+---
+processes:
+  - name: echo-it
+    request:
+      commonHeader: &ch
+        originatorId: sdnc
+        requestId: "1234"
+        subRequestId: "1234-12234"
+      actionIdentifiers: &ai
+        blueprintName: echo_test
+        blueprintVersion: "1.0.0"
+        actionName: echo
+        mode: sync
+      payload:
+        echo-request:
+          echo-properties:
+            echoed-message: &message "Hello World!"
+    expectedResponse:
+      commonHeader: *ch
+      actionIdentifiers: *ai
+      status:
+        code: 200
+        eventType: EVENT_COMPONENT_EXECUTED
+        errorMessage: null
+        message: success
+      payload:
+        echo-response: {}
+      stepData:
+        name: echo
+        properties:
+          resource-assignment-params:
+            echo: *message
+          status: success
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Definitions/policy_types.json b/components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Definitions/policy_types.json
new file mode 100644 (file)
index 0000000..1e44cc7
--- /dev/null
@@ -0,0 +1,3 @@
+{
+  "policy_types" : { }
+}
\ No newline at end of file
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Definitions/relationship_types.json b/components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Definitions/relationship_types.json
new file mode 100644 (file)
index 0000000..4ddd7a5
--- /dev/null
@@ -0,0 +1,3 @@
+{
+  "relationship_types" : { }
+}
\ No newline at end of file
@@ -37,7 +37,7 @@ class RestconfConfigDeploy(AbstractScriptComponentFunction):
             try:
                 # mount the device
                 mount_payload = restconf_client.resolve_and_generate_message_from_template_prefix("config-deploy")
-                restconf_client.mount_device(web_client_service, pnf_id, mount_payload)
+                restconf_client.mount_device(web_client_service, pnf_id, mount_payload, "application/json")
 
                 # log the current configuration subtree
                 current_configuration = restconf_client.retrieve_device_configuration_subtree(
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Templates/config-deploy-restconf-mount-template.vtl b/components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Templates/config-deploy-restconf-mount-template.vtl
new file mode 100644 (file)
index 0000000..8098b05
--- /dev/null
@@ -0,0 +1,18 @@
+{
+  "node": [
+    {
+      "node-id": "${pnf-id}",
+      "netconf-node-topology:protocol": {
+        "name": "TLS"
+      },
+      "netconf-node-topology:host": "${pnf-ipv4-address}",
+      "netconf-node-topology:key-based": {
+        "username": "netconf",
+        "key-id": "ODL_private_key_0"
+      },
+      "netconf-node-topology:port": 6513,
+      "netconf-node-topology:tcp-only": false,
+      "netconf-node-topology:max-connection-attempts": 5
+    }
+  ]
+}
\ No newline at end of file
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Tests/uat.yaml b/components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Tests/uat.yaml
new file mode 100644 (file)
index 0000000..37029e1
--- /dev/null
@@ -0,0 +1,126 @@
+%YAML 1.1
+---
+processes:
+  - name: config-assign
+    request:
+      commonHeader: &commonHeader
+        originatorId: sdnc
+        requestId: "123456-1000"
+        subRequestId: sub-123456-1000
+      actionIdentifiers: &assign-ai
+        blueprintName: configuration_over_restconf
+        blueprintVersion: "1.0.0"
+        actionName: config-assign
+        mode: sync
+      payload:
+        config-assign-request:
+          resolution-key: &resKey "RES-KEY 61"
+          config-assign-properties:
+            service-instance-id: siid_1234
+            pnf-id: &pnfId pnf-id-2019-07-12
+            pnf-ipv4-address: &pnfAddress "192.168.100.11"
+            service-model-uuid: service-model-uuid
+            pnf-customization-uuid: pnf-customization-uuid
+    expectedResponse:
+      commonHeader: *commonHeader
+      actionIdentifiers: *assign-ai
+      status:
+        code: 200
+        eventType: EVENT_COMPONENT_EXECUTED
+        errorMessage: null
+        message: success
+      payload:
+        config-assign-response: {}
+      stepData:
+        name: config-assign
+        properties:
+          resource-assignment-params:
+            config-assign: &assignPatch
+              ietf-restconf:yang-patch:
+                patch-id: patch-1
+                edit:
+                  - edit-id: edit1
+                    operation: merge
+                    target: /
+                    value: { netconflist: { netconf: [ { netconf-id: "10", netconf-param: "1000" }]}}
+                  - edit-id: edit2
+                    operation: merge
+                    target: /
+                    value: { netconflist: { netconf: [ { netconf-id: "20", netconf-param: "2000" }]}}
+                  - edit-id: edit3
+                    operation: merge
+                    target: /
+                    value: { netconflist: { netconf: [ { netconf-id: "30", netconf-param: "3000" }]}}
+          status: success
+  - name: config-deploy
+    request:
+      commonHeader: *commonHeader
+      actionIdentifiers: &deploy-ai
+        actionName: config-deploy
+        blueprintName: configuration_over_restconf
+        blueprintVersion: "1.0.0"
+        mode: sync
+      payload:
+        config-deploy-request:
+          resolution-key: *resKey
+          config-deploy-properties:
+            service-instance-id: siid_1234
+            pnf-id: *pnfId
+            pnf-ipv4-address: *pnfAddress
+            service-model-uuid: service-model-uuid
+            pnf-customization-uuid: pnf-customization-uuid
+    expectedResponse:
+      commonHeader: *commonHeader
+      actionIdentifiers: *deploy-ai
+      payload:
+        config-deploy-response: {}
+      status:
+        code: 200
+        errorMessage: null
+        eventType: EVENT_COMPONENT_EXECUTED
+        message: success
+      stepData:
+        name: config-deploy
+        properties:
+          response-data: ""
+          status: success
+
+external-services:
+  - selector: sdncodl
+    expectations:
+      - request:
+          method: PUT
+          path: &configUri [ restconf/config, &nodeIdentifier [network-topology:network-topology/topology/topology-netconf/node, *pnfId]]
+          content-type: application/json
+          body:
+            node:
+              - node-id: *pnfId
+                netconf-node-topology:protocol: { name: TLS }
+                netconf-node-topology:host: *pnfAddress
+                netconf-node-topology:key-based:
+                  username: netconf
+                  key-id: ODL_private_key_0
+                netconf-node-topology:port: 6513
+                netconf-node-topology:tcp-only: false
+                netconf-node-topology:max-connection-attempts: 5
+        response:
+          status: 201
+      - request:
+          method: GET
+          path: [ restconf/operational, *nodeIdentifier]
+        response:
+          body:
+            node: [ { netconf-node-topology:connection-status: connected }]
+      - request:
+          method: GET
+          path: [*configUri, &configletResourcePath yang-ext:mount/mynetconf:netconflist]
+        response:
+          body: {}
+      - request:
+          method: PATCH
+          path: [*configUri, *configletResourcePath]
+          content-type: application/yang.patch+json
+          body: *assignPatch
+      - request:
+          method: DELETE
+          path: *configUri
index 3e6f710..034ed60 100644 (file)
                 <artifactId>commons-io</artifactId>
                 <version>2.6</version>
             </dependency>
-            <dependency>
-                <groupId>org.apache.commons</groupId>
-                <artifactId>commons-compress</artifactId>
-                <version>1.15</version>
-            </dependency>
             <dependency>
                 <groupId>org.apache.velocity</groupId>
                 <artifactId>velocity</artifactId>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
         </dependency>
-        <dependency>
-            <groupId>org.apache.commons</groupId>
-            <artifactId>commons-compress</artifactId>
-        </dependency>
         <dependency>
             <groupId>com.jayway.jsonpath</groupId>
             <artifactId>json-path</artifactId>
index 43f2a02..927c1fe 100644 (file)
@@ -43,9 +43,9 @@ class RestconfClient:
         return ResourceResolutionExtensionsKt.storedContentFromResolvedArtifact(self.__component_function, key,
                                                                                 artifact_template)
 
-    def mount_device(self, web_client_service, nf_id, mount_payload):
+    def mount_device(self, web_client_service, nf_id, mount_payload, content_type="application/xml"):
         self.__log.debug("mounting device {}", nf_id)
-        headers = {"Content-Type": "application/xml"}
+        headers = {"Content-Type": content_type}
         url = self.__base_odl_url + nf_id
         self.__log.debug("sending mount request, url: {}", url)
         web_client_service.exchangeResource("PUT", url, mount_payload, headers)
index 4e48730..a504ce3 100755 (executable)
             <artifactId>reactor-test</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.yaml</groupId>
+            <artifactId>snakeyaml</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.nhaarman.mockitokotlin2</groupId>
+            <artifactId>mockito-kotlin</artifactId>
+            <version>2.1.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     <build>
         <resources>
diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintsAcceptanceTests.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintsAcceptanceTests.kt
new file mode 100644 (file)
index 0000000..0a57277
--- /dev/null
@@ -0,0 +1,280 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2019 Nordix Foundation.
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.ccsdk.cds.blueprintsprocessor
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.nhaarman.mockitokotlin2.any
+import com.nhaarman.mockitokotlin2.argThat
+import com.nhaarman.mockitokotlin2.atLeast
+import com.nhaarman.mockitokotlin2.atLeastOnce
+import com.nhaarman.mockitokotlin2.eq
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.verify
+import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
+import com.nhaarman.mockitokotlin2.whenever
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestLibConstants
+import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BluePrintRestLibPropertyService
+import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService
+import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService.WebClientResponse
+import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintArchiveUtils.Companion.compressToBytes
+import org.skyscreamer.jsonassert.JSONAssert
+import org.skyscreamer.jsonassert.JSONCompareMode
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.boot.test.mock.mockito.MockBean
+import org.springframework.core.io.ByteArrayResource
+import org.springframework.core.io.Resource
+import org.springframework.http.MediaType
+import org.springframework.test.context.ContextConfiguration
+import org.springframework.test.context.TestPropertySource
+import org.springframework.test.context.junit4.rules.SpringClassRule
+import org.springframework.test.context.junit4.rules.SpringMethodRule
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.yaml.snakeyaml.Yaml
+import reactor.core.publisher.Mono
+import java.io.File
+import java.nio.file.Path
+import java.nio.file.Paths
+import kotlin.test.BeforeTest
+import kotlin.test.Test
+
+@RunWith(Parameterized::class)
+// Set blueprintsprocessor.httpPort=0 to trigger a random port selection
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
+@AutoConfigureWebTestClient(timeout = "PT10S")
+@ContextConfiguration(initializers = [
+    WorkingFoldersInitializer::class,
+    TestSecuritySettings.ServerContextInitializer::class
+])
+@TestPropertySource(locations = ["classpath:application-test.properties"])
+@Suppress("UNCHECKED_CAST")
+class BlueprintsAcceptanceTests(private val blueprintName: String, private val filename: String) {
+
+    companion object {
+        const val UAT_BLUEPRINTS_BASE_DIR = "../../../components/model-catalog/blueprint-model/uat-blueprints"
+        const val EMBEDDED_UAT_FILE = "Tests/uat.yaml"
+
+        @ClassRule
+        @JvmField
+        val springClassRule = SpringClassRule()
+
+        val log: Logger = LoggerFactory.getLogger(BlueprintsAcceptanceTests::class.java)
+
+        @Parameterized.Parameters(name = "{index} {0}")
+        @JvmStatic
+        fun filenames(): List<Array<String>> {
+            return File(UAT_BLUEPRINTS_BASE_DIR)
+                    .listFiles { file -> file.isDirectory && File(file, EMBEDDED_UAT_FILE).isFile }
+                    ?.map { file -> arrayOf(file.nameWithoutExtension, file.canonicalPath) }
+                    ?: emptyList()
+        }
+    }
+
+    @Rule
+    @JvmField
+    val springMethodRule = SpringMethodRule()
+
+    @MockBean(name = RestLibConstants.SERVICE_BLUEPRINT_REST_LIB_PROPERTY)
+    lateinit var restClientFactory: BluePrintRestLibPropertyService
+
+    @Autowired
+    // Bean is created programmatically by {@link WorkingFoldersInitializer#initialize(String)}
+    @Suppress("SpringJavaInjectionPointsAutowiringInspection")
+    lateinit var tempFolder: ExtendedTemporaryFolder
+
+    @Autowired
+    lateinit var webTestClient: WebTestClient
+
+    @Autowired
+    lateinit var mapper: ObjectMapper
+
+    @BeforeTest
+    fun cleanupTemporaryFolder() {
+        tempFolder.deleteAllFiles()
+    }
+
+    @Test
+    fun testBlueprint() {
+        val yaml: Map<String, *> = loadYaml(Paths.get(filename, EMBEDDED_UAT_FILE))
+
+        uploadBlueprint(blueprintName)
+
+        // Configure mocked external services
+        val services = yaml["external-services"] as List<Map<String, *>>? ?: emptyList()
+        val expectationPerClient = services.map { service ->
+            val selector = service["selector"] as String
+            val expectations = (service["expectations"] as List<Map<String, *>>).map {
+                parseExpectation(it)
+            }
+            val mockClient = createRestClientMock(selector, expectations)
+            mockClient to expectations
+        }.toMap()
+
+        // Run processes
+        for (process in (yaml["processes"] as List<Map<String, *>>)) {
+            val processName = process["name"]
+            log.info("Executing process '$processName'")
+            val request = mapper.writeValueAsString(process["request"])
+            val expectedResponse = mapper.writeValueAsString(process["expectedResponse"])
+            processBlueprint(request, expectedResponse)
+        }
+
+        // Validate request payloads
+        for ((mockClient, expectations) in expectationPerClient) {
+            expectations.forEach { expectation ->
+                verify(mockClient, atLeastOnce()).exchangeResource(
+                        eq(expectation.method),
+                        eq(expectation.path),
+                        argThat { assertJsonEqual(expectation.expectedRequestBody, this) },
+                        expectation.requestHeadersMatcher())
+            }
+            // Don't mind the invocations to the overloaded exchangeResource(String, String, String)
+            verify(mockClient, atLeast(0)).exchangeResource(any(), any(), any())
+            verifyNoMoreInteractions(mockClient)
+        }
+    }
+
+    private fun createRestClientMock(selector: String, restExpectations: List<RestExpectation>): BlueprintWebClientService {
+        val restClient = mock<BlueprintWebClientService>(verboseLogging = true)
+
+        // Delegates to overloaded exchangeResource(String, String, String, Map<String, String>)
+        whenever(restClient.exchangeResource(any(), any(), any()))
+                .thenAnswer { invocation ->
+                    val method = invocation.arguments[0] as String
+                    val path = invocation.arguments[1] as String
+                    val request = invocation.arguments[2] as String
+                    restClient.exchangeResource(method, path, request, emptyMap())
+                }
+        for (expectation in restExpectations) {
+            whenever(restClient.exchangeResource(
+                    eq(expectation.method),
+                    eq(expectation.path),
+                    any(),
+                    any()))
+                    .thenReturn(WebClientResponse(expectation.statusCode, expectation.responseBody))
+        }
+
+        whenever(restClientFactory.blueprintWebClientService(selector))
+                .thenReturn(restClient)
+        return restClient
+    }
+
+    private fun uploadBlueprint(blueprintName: String) {
+        val body = toMultiValueMap("file", getBlueprintAsResource(blueprintName))
+        webTestClient
+                .post()
+                .uri("/api/v1/execution-service/upload")
+                .header("Authorization", TestSecuritySettings.clientAuthToken())
+                .syncBody(body)
+                .exchange()
+                .expectStatus().isOk
+    }
+
+    private fun processBlueprint(request: String, expectedResponse: String) {
+        webTestClient
+                .post()
+                .uri("/api/v1/execution-service/process")
+                .header("Authorization", TestSecuritySettings.clientAuthToken())
+                .contentType(MediaType.APPLICATION_JSON_UTF8)
+                .body(Mono.just(request), String::class.java)
+                .exchange()
+                .expectStatus().isOk
+                .expectBody()
+                .json(expectedResponse)
+    }
+
+    private fun getBlueprintAsResource(blueprintName: String): Resource {
+        val baseDir = Paths.get(UAT_BLUEPRINTS_BASE_DIR, blueprintName)
+        val zipBytes = compressToBytes(baseDir)
+        return object : ByteArrayResource(zipBytes) {
+            // Filename has to be returned in order to be able to post
+            override fun getFilename() = "$blueprintName.zip"
+        }
+    }
+
+    private fun loadYaml(path: Path): Map<String, Any> {
+        return path.toFile().reader().use { reader ->
+            Yaml().load(reader)
+        }
+    }
+
+    private fun assertJsonEqual(expected: Any, actual: String): Boolean {
+        if (actual != expected) {
+            // assertEquals throws an exception whenever match fails
+            JSONAssert.assertEquals(mapper.writeValueAsString(expected), actual, JSONCompareMode.LENIENT)
+        }
+        return true
+    }
+
+    private fun parseExpectation(expectation: Map<String, *>): RestExpectation {
+        val request = expectation["request"] as Map<String, Any>
+        val method = request["method"] as String
+        val path = joinPath(request.getValue("path"))
+        val contentType = request["content-type"] as String?
+        val requestBody = request.getOrDefault("body", "")
+
+        val response = expectation["response"] as Map<String, Any>? ?: emptyMap()
+        val status = response["status"] as Int? ?: 200
+        val responseBody = when (val body = response["body"] ?: "") {
+            is String -> body
+            else -> mapper.writeValueAsString(body)
+        }
+
+        return RestExpectation(method, path, contentType, requestBody, status, responseBody)
+    }
+
+    /**
+     * Join a multilevel lists of strings.
+     * Example: joinPath(listOf("a", listOf("b", "c"), "d")) will result in "a/b/c/d".
+     */
+    private fun joinPath(any: Any): String {
+        fun recursiveJoin(any: Any, sb: StringBuilder): StringBuilder {
+            when (any) {
+                is List<*> -> any.filterNotNull().forEach { recursiveJoin(it, sb) }
+                is String -> {
+                    if (sb.isNotEmpty()) {
+                        sb.append('/')
+                    }
+                    sb.append(any)
+                }
+                else -> throw IllegalArgumentException("Unsupported type: ${any.javaClass}")
+            }
+            return sb
+        }
+
+        return recursiveJoin(any, StringBuilder()).toString()
+    }
+
+    data class RestExpectation(val method: String, val path: String, val contentType: String?,
+                               val expectedRequestBody: Any,
+                               val statusCode: Int, val responseBody: String) {
+
+        fun requestHeadersMatcher(): Map<String, String> {
+            return if (contentType != null) eq(mapOf("Content-Type" to contentType)) else any()
+        }
+    }
+}
\ No newline at end of file
diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/CollectionUtils2.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/CollectionUtils2.kt
new file mode 100644 (file)
index 0000000..63d64ca
--- /dev/null
@@ -0,0 +1,31 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2019 Nordix Foundation.
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.ccsdk.cds.blueprintsprocessor
+
+import org.springframework.util.CollectionUtils
+import org.springframework.util.MultiValueMap
+
+
+/**
+ * Convenient method to create a single-entry MultiValueMap.
+ */
+fun <K, V> toMultiValueMap(key: K, vararg values: V): MultiValueMap<K, V> {
+    return CollectionUtils.toMultiValueMap(mapOf(key to values.asList()))
+}
diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/ExtendedTemporaryFolder.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/ExtendedTemporaryFolder.kt
new file mode 100644 (file)
index 0000000..4576f27
--- /dev/null
@@ -0,0 +1,40 @@
+package org.onap.ccsdk.cds.blueprintsprocessor
+
+import org.junit.rules.TemporaryFolder
+import java.io.File
+import java.io.IOException
+import java.nio.file.FileVisitResult
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.SimpleFileVisitor
+import java.nio.file.attribute.BasicFileAttributes
+import javax.annotation.PreDestroy
+
+class ExtendedTemporaryFolder {
+    private val tempFolder = TemporaryFolder()
+
+    init {
+        tempFolder.create()
+    }
+
+    @PreDestroy
+    fun delete() = tempFolder.delete()
+
+    /**
+     * A delegate to org.junit.rules.TemporaryFolder.TemporaryFolder.newFolder(String).
+     */
+    fun newFolder(folder: String): File = tempFolder.newFolder(folder)
+
+    /**
+     * Delete all files under the root temporary folder recursively. The folders are preserved.
+     */
+    fun deleteAllFiles() {
+        Files.walkFileTree(tempFolder.root.toPath(), object : SimpleFileVisitor<Path>() {
+            @Throws(IOException::class)
+            override fun visitFile(file: Path?, attrs: BasicFileAttributes?): FileVisitResult {
+                file?.toFile()?.delete()
+                return FileVisitResult.CONTINUE
+            }
+        })
+    }
+}
diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/TestSecuritySettings.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/TestSecuritySettings.kt
new file mode 100644 (file)
index 0000000..f7ab255
--- /dev/null
@@ -0,0 +1,45 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2019 Nordix Foundation.
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.ccsdk.cds.blueprintsprocessor
+
+import org.springframework.context.ApplicationContextInitializer
+import org.springframework.context.ConfigurableApplicationContext
+import org.springframework.test.context.support.TestPropertySourceUtils
+import org.springframework.util.Base64Utils
+import java.nio.charset.StandardCharsets
+
+class TestSecuritySettings {
+    companion object {
+        private const val authUsername = "walter.white"
+        private const val authPassword = "Heisenberg"
+
+        fun clientAuthToken() =
+                "Basic " + Base64Utils.encodeToString("$authUsername:$authPassword".toByteArray(StandardCharsets.UTF_8))
+    }
+
+    class ServerContextInitializer : ApplicationContextInitializer<ConfigurableApplicationContext> {
+        override fun initialize(context: ConfigurableApplicationContext) {
+            TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context,
+                    "security.user.name=$authUsername",
+                    "security.user.password={noop}$authPassword"
+            )
+        }
+    }
+}
diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/WorkingFoldersInitializer.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/WorkingFoldersInitializer.kt
new file mode 100644 (file)
index 0000000..37615cb
--- /dev/null
@@ -0,0 +1,48 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2019 Nordix Foundation.
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.ccsdk.cds.blueprintsprocessor
+
+import org.springframework.beans.factory.support.BeanDefinitionBuilder
+import org.springframework.beans.factory.support.BeanDefinitionRegistry
+import org.springframework.context.ApplicationContextInitializer
+import org.springframework.context.ConfigurableApplicationContext
+import org.springframework.stereotype.Component
+import org.springframework.test.context.support.TestPropertySourceUtils
+
+@Component
+class WorkingFoldersInitializer : ApplicationContextInitializer<ConfigurableApplicationContext> {
+
+    override fun initialize(context: ConfigurableApplicationContext) {
+        val tempFolder = ExtendedTemporaryFolder()
+        val properties = listOf("Deploy", "Archive", "Working")
+                .map { "blueprintsprocessor.blueprint${it}Path=${tempFolder.newFolder(it)}" }
+                .toTypedArray()
+        TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context, *properties)
+        // Expose tempFolder as a bean so it can be accessed via DI
+        registerSingleton(context, "tempFolder", ExtendedTemporaryFolder::class.java, tempFolder)
+    }
+
+    @Suppress("SameParameterValue")
+    private fun <T> registerSingleton(context: ConfigurableApplicationContext,
+                                      beanName: String, beanClass: Class<T>, instance: T) {
+        val builder = BeanDefinitionBuilder.genericBeanDefinition(beanClass) { instance }
+        (context.beanFactory as BeanDefinitionRegistry).registerBeanDefinition(beanName, builder.beanDefinition)
+    }
+}
diff --git a/ms/blueprintsprocessor/application/src/test/resources/application-test.properties b/ms/blueprintsprocessor/application/src/test/resources/application-test.properties
new file mode 100644 (file)
index 0000000..b8b80f2
--- /dev/null
@@ -0,0 +1,50 @@
+#
+# Copyright © 2019 Nordix Foundation.
+#
+# 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.
+#
+
+spring.http.log-request-details=true
+
+blueprintsprocessor.httpPort=0
+blueprintsprocessor.grpcEnable=true
+blueprintsprocessor.grpcPort=0
+
+blueprintsprocessor.db.primary.url=jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_DELAY=-1
+blueprintsprocessor.db.primary.username=sa
+blueprintsprocessor.db.primary.password=
+blueprintsprocessor.db.primary.driverClassName=org.h2.Driver
+blueprintsprocessor.db.primary.hibernateHbm2ddlAuto=create-drop
+blueprintsprocessor.db.primary.hibernateDDLAuto=update
+blueprintsprocessor.db.primary.hibernateNamingStrategy=org.hibernate.cfg.ImprovedNamingStrategy
+blueprintsprocessor.db.primary.hibernateDialect=org.hibernate.dialect.H2Dialect
+
+# The properties bellow are set programmatically
+#blueprintsprocessor.blueprintDeployPath=
+#blueprintsprocessor.blueprintArchivePath=
+#blueprintsprocessor.blueprintWorkingPath=
+#security.user.name=
+#security.user.password=
+
+# Python executor
+blueprints.processor.functions.python.executor.executionPath=../../../components/scripts/python/ccsdk_blueprints
+blueprints.processor.functions.python.executor.modulePaths=\
+  ../../../components/scripts/python/ccsdk_blueprints,\
+  ../../../components/scripts/python/ccsdk_netconf,\
+  ../../../components/scripts/python/ccsdk_restconf
+
+# Executor Options
+blueprintsprocessor.cliExecutor.enabled=true
+blueprintprocessor.netconfExecutor.enabled=true
+
+blueprintsprocessor.restconfEnabled=true
\ No newline at end of file
index 16e7d3d..eaa51c0 100644 (file)
@@ -1,5 +1,6 @@
 <!--\r
   ~ Copyright © 2017-2018 AT&T Intellectual Property.\r
+  ~ Modifications Copyright (C) 2019 Nordix Foundation.\r
   ~\r
   ~ Licensed under the Apache License, Version 2.0 (the "License");\r
   ~ you may not use this file except in compliance with the License.\r
 \r
 <configuration>\r
     <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">\r
-        <!-- encoders are assigned the type\r
-             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\r
         <encoder>\r
-            <pattern>%d{HH:mm:ss.SSS} %-5level [%thread]  %logger{50} - %msg%n</pattern>\r
+            <pattern>%d{HH:mm:ss.SSS} %-5level %-40.40logger{39} : %msg%n</pattern>\r
         </encoder>\r
     </appender>\r
 \r
+    <logger name="org.springframework.web.HttpLogging" level="trace"/>\r
+    <logger name="org.springframework.web.reactive.function.client.ExchangeFunctions" level="trace"/>\r
 \r
-    <logger name="org.springframework" level="warn"/>\r
-    <logger name="org.hibernate" level="info"/>\r
-    <logger name="org.onap.ccsdk.cds.blueprintsprocessor" level="info"/>\r
+    <!-- Helpful to optimize Spring Context caching to speed-up tests\r
+         and prevent resorting to @DirtiesContext as much as possible -->\r
+    <logger name="org.springframework.test.context.cache" level="debug"/>\r
 \r
-    <root level="warn">\r
+    <!-- Please refer to https://thoughts-on-java.org/hibernate-logging-guide/\r
+         for a lengthy discussion on good Hibernate logging practices -->\r
+    <logger name="org.hibernate.SQL" level="debug"/>\r
+    <logger name="org.hibernate.type.descriptor.sql" level="trace"/>\r
+\r
+    <root level="info">\r
         <appender-ref ref="STDOUT"/>\r
     </root>\r
 \r
index da4d993..4f68657 100644 (file)
@@ -37,13 +37,13 @@ import org.springframework.stereotype.Service
 open class BluePrintRestLibPropertyService(private var bluePrintProperties:
                                            BluePrintProperties) {
 
-    fun blueprintWebClientService(jsonNode: JsonNode):
+    open fun blueprintWebClientService(jsonNode: JsonNode):
             BlueprintWebClientService {
         val restClientProperties = restClientProperties(jsonNode)
         return blueprintWebClientService(restClientProperties)
     }
 
-    fun blueprintWebClientService(selector: String): BlueprintWebClientService {
+    open fun blueprintWebClientService(selector: String): BlueprintWebClientService {
         val prefix = "blueprintsprocessor.restclient.$selector"
         val restClientProperties = restClientProperties(prefix)
         return blueprintWebClientService(restClientProperties)
index da21970..57d24a2 100755 (executable)
                 <artifactId>commons-io</artifactId>
                 <version>2.6</version>
             </dependency>
-            <dependency>
-                <groupId>org.apache.commons</groupId>
-                <artifactId>commons-compress</artifactId>
-                <version>1.15</version>
-            </dependency>
             <dependency>
                 <groupId>org.apache.velocity</groupId>
                 <artifactId>velocity</artifactId>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
         </dependency>
-        <dependency>
-            <groupId>org.apache.commons</groupId>
-            <artifactId>commons-compress</artifactId>
-        </dependency>
         <dependency>
             <groupId>com.jayway.jsonpath</groupId>
             <artifactId>json-path</artifactId>
index 18091e6..518e9b2 100644 (file)
@@ -52,7 +52,7 @@ fun File.compress(targetZipFileName: String): File {
  * Compress the current Dir to the target zip file and return the target zip file
  */
 fun File.compress(targetZipFile: File): File {
-    BluePrintArchiveUtils.compress(this, targetZipFile, true)
+    BluePrintArchiveUtils.compress(this, targetZipFile)
     return targetZipFile
 }
 
index d367041..dcfa07f 100755 (executable)
@@ -1,6 +1,7 @@
 /*
  * Copyright © 2017-2018 AT&T Intellectual Property.
  * Modifications Copyright © 2019 Bell Canada.
+ * Modifications Copyright © 2019 Nordix Foundation.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 package org.onap.ccsdk.cds.controllerblueprints.core.utils
 
-import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
-import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
-import org.apache.commons.io.IOUtils
+import com.google.common.base.Predicates
 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
 import org.slf4j.LoggerFactory
 import java.io.BufferedInputStream
+import java.io.ByteArrayOutputStream
 import java.io.File
-import java.io.FileInputStream
+import java.io.FileOutputStream
 import java.io.IOException
+import java.io.OutputStream
 import java.nio.charset.Charset
+import java.nio.file.FileVisitResult
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.SimpleFileVisitor
+import java.nio.file.attribute.BasicFileAttributes
+import java.util.function.Predicate
+import java.util.zip.Deflater
+import java.util.zip.ZipEntry
 import java.util.zip.ZipFile
+import java.util.zip.ZipOutputStream
 
 class BluePrintArchiveUtils {
 
@@ -39,14 +49,14 @@ class BluePrintArchiveUtils {
          *
          * @param source the base directory
          * @param destination the output filename
-         * @param absolute store absolute filepath (from directory) or only filename
          * @return True if OK
          */
-        fun compress(source: File, destination: File, absolute: Boolean): Boolean {
+        fun compress(source: File, destination: File): Boolean {
             try {
                 destination.createNewFile()
-                ZipArchiveOutputStream(destination).use {
-                    recurseFiles(source, source, it, absolute)
+                val ignoreZipFiles = Predicate<Path> { path -> !path.endsWith(".zip") && !path.endsWith(".ZIP") }
+                FileOutputStream(destination).use { out ->
+                    compressFolder(source.toPath(), out, pathFilter = ignoreZipFiles)
                 }
             } catch (e: Exception) {
                 log.error("Fail to compress folder($source) to path(${destination.path})", e)
@@ -56,40 +66,61 @@ class BluePrintArchiveUtils {
         }
 
         /**
-         * Recursive traversal to add files
-         *
-         * @param root
-         * @param file
-         * @param zaos
-         * @param absolute
-         * @throws IOException
+         * In-memory compress an entire folder.
          */
-        @Throws(IOException::class)
-        private fun recurseFiles(root: File, file: File, zaos: ZipArchiveOutputStream,
-                                 absolute: Boolean) {
-            if (file.isDirectory) {
-                // recursive call
-                val files = file.listFiles()
-                for (fileChild in files!!) {
-                    recurseFiles(root, fileChild, zaos, absolute)
-                }
-            } else if (!file.name.endsWith(".zip") && !file.name.endsWith(".ZIP")) {
-                val filename = if (absolute) {
-                    file.absolutePath.substring(root.absolutePath.length)
-                } else {
-                    file.name
-                }
-                val zae = ZipArchiveEntry(filename)
-                zae.size = file.length()
-                zaos.putArchiveEntry(zae)
-                FileInputStream(file).use {
-                    IOUtils.copy(it, zaos)
-                    it.close()
-                }
-                zaos.closeArchiveEntry()
-            }
+        fun compressToBytes(baseDir: Path, compressionLevel: Int = Deflater.NO_COMPRESSION): ByteArray {
+            return compressFolder(baseDir, ByteArrayOutputStream(), compressionLevel = compressionLevel)
+                    .toByteArray()
         }
 
+        /**
+         * Compress an entire folder.
+         *
+         * @param baseDir path of base folder to be packaged.
+         * @param output the output stream
+         * @param pathFilter filter to ignore files based on its path.
+         * @param compressionLevel the wanted compression level.
+         * @param fixedModificationTime to force every entry to have this modification time.
+         * Useful for reproducible operations, like tests, for example.
+         */
+        private fun <T> compressFolder(baseDir: Path, output: T,
+                                       pathFilter: Predicate<Path> = Predicates.alwaysTrue(),
+                                       compressionLevel: Int = Deflater.DEFAULT_COMPRESSION,
+                                       fixedModificationTime: Long? = null): T
+                where T : OutputStream {
+            ZipOutputStream(output)
+                    .apply { setLevel(compressionLevel) }
+                    .use { zos ->
+                        Files.walkFileTree(baseDir, object : SimpleFileVisitor<Path>() {
+                            @Throws(IOException::class)
+                            override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
+                                if (pathFilter.test(file)) {
+                                    val zipEntry = ZipEntry(baseDir.relativize(file).toString())
+                                    fixedModificationTime?.let {
+                                        zipEntry.time = it
+                                    }
+                                    zipEntry.time = 0;
+                                    zos.putNextEntry(zipEntry)
+                                    Files.copy(file, zos)
+                                    zos.closeEntry()
+                                }
+                                return FileVisitResult.CONTINUE
+                            }
+
+                            @Throws(IOException::class)
+                            override fun preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult {
+                                val zipEntry = ZipEntry(baseDir.relativize(dir).toString() + "/")
+                                fixedModificationTime?.let {
+                                    zipEntry.time = it
+                                }
+                                zos.putNextEntry(zipEntry)
+                                zos.closeEntry()
+                                return FileVisitResult.CONTINUE
+                            }
+                        })
+                    }
+            return output
+        }
 
         fun deCompress(zipFile: File, targetPath: String): File {
             val zip = ZipFile(zipFile, Charset.defaultCharset())
index 9780bbd..b3436a9 100644 (file)
@@ -46,7 +46,7 @@ abstract class BlueprintCatalogServiceImpl(
             workingDir = blueprintFile.absolutePath
             archiveFile = normalizedFile(bluePrintPathConfiguration.blueprintArchivePath, processingId, "cba.zip")
 
-            if (!BluePrintArchiveUtils.compress(blueprintFile, archiveFile, true)) {
+            if (!BluePrintArchiveUtils.compress(blueprintFile, archiveFile)) {
                 throw BluePrintException("Fail to compress blueprint")
             }
         } else {
index d4753e1..a0f8ca9 100644 (file)
@@ -109,7 +109,7 @@ class BluePrintEnhancerUtils {
 
         suspend fun compressToFilePart(enhanceDir: String, archiveDir: String): ResponseEntity<Resource> {
             val compressedFile = normalizedFile(archiveDir, "enhanced-cba.zip")
-            BluePrintArchiveUtils.compress(Paths.get(enhanceDir).toFile(), compressedFile, true)
+            BluePrintArchiveUtils.compress(Paths.get(enhanceDir).toFile(), compressedFile)
             return prepareResourceEntity(compressedFile.name, compressedFile.readBytes())
         }
 
index 1f872c2..d09479b 100644 (file)
@@ -66,7 +66,6 @@ class BluePrintEnhancerServiceImplTest {
             testBaseConfigEnhancementAndValidation()
             testVFWEnhancementAndValidation()
             testGoldenEnhancementAndValidation()
-            testCapabilityRestconfEnhancementAndValidation()
             testRemoteScriptsEnhancementAndValidation()
             testCapabilityCliEnhancementAndValidation()
         }
@@ -87,12 +86,6 @@ class BluePrintEnhancerServiceImplTest {
         testComponentInvokeEnhancementAndValidation(basePath, "golden-enhance")
     }
 
-    fun testCapabilityRestconfEnhancementAndValidation() {
-        val basePath = "./../../../../components/model-catalog/blueprint-model/test-blueprint/capability_restconf"
-        testComponentInvokeEnhancementAndValidation(basePath, "capability_restconf-enhance")
-
-    }
-
     fun testRemoteScriptsEnhancementAndValidation() {
         val basePath = "./../../../../components/model-catalog/blueprint-model/test-blueprint/remote_scripts"
         testComponentInvokeEnhancementAndValidation(basePath, "remote_scripts-enhance")
index f2f4ff1..43208b9 100644 (file)
                 <artifactId>commons-io</artifactId>
                 <version>2.6</version>
             </dependency>
-            <dependency>
-                <groupId>org.apache.commons</groupId>
-                <artifactId>commons-compress</artifactId>
-                <version>1.15</version>
-            </dependency>
             <dependency>
                 <groupId>org.apache.velocity</groupId>
                 <artifactId>velocity</artifactId>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
         </dependency>
-        <dependency>
-            <groupId>org.apache.commons</groupId>
-            <artifactId>commons-compress</artifactId>
-        </dependency>
         <dependency>
             <groupId>com.jayway.jsonpath</groupId>
             <artifactId>json-path</artifactId>