Merge SW Upgrade Blueprint into PNF_AAI and create one UAT BP for PNF 94/101994/11
authorgummar <raj.gumma@est.tech>
Tue, 18 Feb 2020 18:54:44 +0000 (18:54 +0000)
committerKAPIL SINGAL <ks220y@att.com>
Wed, 26 Feb 2020 15:23:42 +0000 (15:23 +0000)
UAT: Add support to multiple responses for a single request
     Set property IN_UAT=1 during UAT execution
     so blueprints can tune their settings to values more
     suitable for testing (like timeouts)
     Add 'times' field to specify expected number of invocations
     Add UAT blueprint script for PNF SW Upgrade UC
     Add current thread check for Hazelcast distributed lock
     Resolve URI before returning

Issue-ID: CCSDK-2091
Change-Id: Id256bad043488f88f1b60015ebf9ade4be607fa2
Signed-off-by: gummar <raj.gumma@est.tech>
37 files changed:
components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/PNF_CDS_RESTCONF.json [new file with mode: 0644]
components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/artifact_types.json [moved from components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/artifact_types.json with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/config-assign-pnf-mapping.json [moved from components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/config-assign-pnf-mapping.json with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/config-deploy-pnf-mapping.json [moved from components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/config-deploy-pnf-mapping.json with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/data_types.json [new file with mode: 0644]
components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/node_types.json [moved from components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/node_types.json with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/pnf-software-upgrade-mapping.json [new file with mode: 0644]
components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/policy_types.json [moved from components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/policy_types.json with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/relationship_types.json [moved from components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/relationship_types.json with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/resources_definition_types.json [moved from components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/resources_definition_types.json with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Environments/source-db.properties [moved from components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Environments/source-db.properties with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Environments/source-rest.properties [moved from components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Environments/source-rest.properties with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Plans/CONFIG_configAssign.xml [moved from components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Plans/CONFIG_configAssign.xml with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Plans/CONFIG_configDeploy.xml [moved from components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Plans/CONFIG_configDeploy.xml with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Plans/CONFIG_upgradeSoftware.xml [new file with mode: 0644]
components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Scripts/kotlin/RestconfConfigDeploy.kt [moved from components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Scripts/kotlin/RestconfConfigDeploy.kt with 96% similarity]
components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Scripts/kotlin/RestconfSoftwareUpgrade.kt [new file with mode: 0644]
components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/TOSCA-Metadata/TOSCA.meta [new file with mode: 0644]
components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Templates/config-assign-restconf-configlet-template.vtl [moved from components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Templates/config-assign-restconf-configlet-template.vtl with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Templates/pnf-swug-config-template.vtl [new file with mode: 0644]
components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Templates/pnf-swug-download-ne-sw-template.vtl [new file with mode: 0644]
components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Templates/restconf-mount-template.vtl [moved from components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Templates/config-deploy-restconf-mount-template.vtl with 100% similarity]
components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Tests/uat.yaml [new file with mode: 0644]
components/model-catalog/blueprint-model/uat-blueprints/README.md
components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Tests/uat.yaml
components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/data_types.json [deleted file]
components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/pnf_config_aai.json [deleted file]
components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/TOSCA-Metadata/TOSCA.meta [deleted file]
components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Tests/uat.yaml [deleted file]
ms/blueprintsprocessor/application/src/main/resources/application-local.yml
ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/UatServicesTest.kt
ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/InvalidUatDefinition.kt [new file with mode: 0644]
ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/UatDefinition.kt
ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/UatExecutor.kt
ms/blueprintsprocessor/functions/restconf-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/restconf/executor/RestconfExecutorExtensions.kt
ms/blueprintsprocessor/modules/commons/processor-core/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/core/cluster/HazlecastClusterService.kt
ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BasicAuthRestClientService.kt

diff --git a/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/PNF_CDS_RESTCONF.json b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/PNF_CDS_RESTCONF.json
new file mode 100644 (file)
index 0000000..2e6c2f5
--- /dev/null
@@ -0,0 +1,424 @@
+{
+  "tosca_definitions_version" : "controller_blueprint_1_0_0",
+  "metadata" : {
+    "template_author" : "Raj Gumma",
+    "author-email" : "raj.gumma@est.tech",
+    "user-groups" : "ADMIN, OPERATION",
+    "template_name" : "PNF_CDS_RESTCONF",
+    "template_version" : "1.0.0",
+    "template_tags" : "PNF, Restconf, config, configuration, software upgrade"
+  },
+  "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"
+  } ],
+  "dsl_definitions" : {
+    "config-deploy-properties" : {
+      "resolution-key" : {
+        "get_input" : "resolution-key"
+      }
+    },
+    "precheck-properties" : {
+      "resolution-key" : {
+        "get_input" : "resolution-key"
+      }
+    },
+    "downloadNeSw-properties" : {
+      "resolution-key" : {
+        "get_input" : "resolution-key"
+      }
+    },
+    "activateNeSw-properties" : {
+      "resolution-key" : {
+        "get_input" : "resolution-key"
+      }
+    },
+    "postcheck-properties" : {
+      "resolution-key" : {
+        "get_input" : "resolution-key"
+      }
+    }
+  },
+  "topology_template" : {
+    "workflows" : {
+      "config-assign" : {
+        "steps" : {
+          "activate-process" : {
+            "description" : "Create a configlet",
+            "target" : "config-assign",
+            "activities" : [ {
+              "call_operation" : ""
+            } ]
+          }
+        },
+        "inputs" : {
+          "resolution-key" : {
+            "required" : true,
+            "type" : "string"
+          },
+          "store-result" : {
+            "required" : true,
+            "type" : "boolean"
+          },
+          "config-assign-properties" : {
+            "description" : "Dynamic PropertyDefinition for workflow(config-assign).",
+            "required" : true,
+            "type" : "dt-config-assign-properties"
+          }
+        }
+      },
+      "config-deploy" : {
+        "steps" : {
+          "activate-process" : {
+            "description" : "Send a configlet to the pnf",
+            "target" : "config-deploy",
+            "activities" : [ {
+              "call_operation" : ""
+            } ]
+          }
+        },
+        "inputs" : {
+          "resolution-key" : {
+            "required" : true,
+            "type" : "string"
+          },
+          "config-deploy-properties" : {
+            "description" : "Dynamic PropertyDefinition for workflow(config-deploy).",
+            "required" : true,
+            "type" : "dt-config-deploy-properties"
+          }
+        }
+      },
+      "precheck" : {
+        "steps" : {
+          "activate-process" : {
+            "description" : "Check if pnf ready for sw upgrade",
+            "target" : "precheck",
+            "activities" : [ {
+              "call_operation" : ""
+            } ]
+          }
+        },
+        "inputs" : {
+          "resolution-key" : {
+            "required" : true,
+            "type" : "string"
+          },
+          "precheck-properties" : {
+            "description" : "Dynamic PropertyDefinition for precheck workflow(software-upgrade).",
+            "required" : true,
+            "type" : "dt-precheck-properties"
+          }
+        }
+      },
+      "downloadNeSw" : {
+        "steps" : {
+          "activate-process" : {
+            "description" : "Trigger download new software for sw upgrade",
+            "target" : "downloadNeSw",
+            "activities" : [ {
+              "call_operation" : ""
+            } ]
+          }
+        },
+        "inputs" : {
+          "resolution-key" : {
+            "required" : true,
+            "type" : "string"
+          },
+          "downloadNeSw-properties" : {
+            "description" : "Dynamic PropertyDefinition for downloadNeSw workflow(software-upgrade).",
+            "required" : true,
+            "type" : "dt-downloadNeSw-properties"
+          }
+        }
+      },
+      "activateNeSw" : {
+        "steps" : {
+          "activate-process" : {
+            "description" : "Trigger activation of target software version for pnf upgrade",
+            "target" : "activateNeSw",
+            "activities" : [ {
+              "call_operation" : ""
+            } ]
+          }
+        },
+        "inputs" : {
+          "resolution-key" : {
+            "required" : true,
+            "type" : "string"
+          },
+          "activateNeSw-properties" : {
+            "description" : "Dynamic PropertyDefinition for activateNeSw workflow(software-upgrade).",
+            "required" : true,
+            "type" : "dt-activateNeSw-properties"
+          }
+        }
+      },
+      "postcheck" : {
+        "steps" : {
+          "activate-process" : {
+            "description" : "Check if pnf upgrade is completed",
+            "target" : "postcheck",
+            "activities" : [ {
+              "call_operation" : ""
+            } ]
+          }
+        },
+        "inputs" : {
+          "resolution-key" : {
+            "required" : true,
+            "type" : "string"
+          },
+          "postcheck-properties" : {
+            "description" : "Dynamic PropertyDefinition for postcheck workflow(software-upgrade).",
+            "required" : true,
+            "type" : "dt-postcheck-properties"
+          }
+        }
+      }
+    },
+    "node_templates" : {
+      "config-assign" : {
+        "type" : "component-resource-resolution",
+        "interfaces" : {
+          "ResourceResolutionComponent" : {
+            "operations" : {
+              "process" : {
+                "inputs" : {
+                  "resolution-key" : {
+                    "get_input" : "resolution-key"
+                  },
+                  "store-result" : true,
+                  "artifact-prefix-names" : [ "config-assign" ]
+                },
+                "outputs" : {
+                  "resource-assignment-params" : {
+                    "get_attribute" : [ "SELF", "assignment-params" ]
+                  },
+                  "status" : "success"
+                }
+              }
+            }
+          }
+        },
+        "artifacts" : {
+          "config-assign-template" : {
+            "type" : "artifact-template-velocity",
+            "file" : "Templates/config-assign-restconf-configlet-template.vtl"
+          },
+          "config-assign-mapping" : {
+            "type" : "artifact-mapping-resource",
+            "file" : "Definitions/config-assign-pnf-mapping.json"
+          }
+        }
+      },
+      "config-deploy" : {
+        "type" : "component-script-executor",
+        "interfaces" : {
+          "ComponentScriptExecutor" : {
+            "operations" : {
+              "process" : {
+                "implementation" : {
+                  "primary" : "component-script",
+                  "timeout" : 180,
+                  "operation_host" : "SELF"
+                },
+                "inputs" : {
+                  "script-type" : "kotlin",
+                  "script-class-reference" : "cba.pnf.config.aai.RestconfConfigDeploy",
+                  "dynamic-properties" : "*config-deploy-properties"
+                },
+                "outputs" : {
+                  "response-data" : "",
+                  "status" : "success"
+                }
+              }
+            }
+          }
+        },
+        "artifacts" : {
+          "config-deploy-template" : {
+            "type" : "artifact-template-velocity",
+            "file" : "Templates/restconf-mount-template.vtl"
+          },
+          "config-deploy-mapping" : {
+            "type" : "artifact-mapping-resource",
+            "file" : "Definitions/config-deploy-pnf-mapping.json"
+          }
+        }
+      },
+      "precheck" : {
+        "type" : "component-script-executor",
+        "interfaces" : {
+          "ComponentScriptExecutor" : {
+            "operations" : {
+              "process" : {
+                "implementation" : {
+                  "primary" : "component-script",
+                  "timeout" : 180,
+                  "operation_host" : "SELF"
+                },
+                "inputs" : {
+                  "script-type" : "kotlin",
+                  "script-class-reference" : "cba.pnf.swug.RestconfSoftwareUpgrade",
+                  "dynamic-properties" : "*precheck-properties"
+                },
+                "outputs" : {
+                  "response-data" : "",
+                  "status" : "success"
+                }
+              }
+            }
+          }
+        },
+        "artifacts" : {
+          "mount-node-template" : {
+            "type" : "artifact-template-velocity",
+            "file" : "Templates/restconf-mount-template.vtl"
+          },
+          "mount-node-mapping" : {
+            "type" : "artifact-mapping-resource",
+            "file" : "Definitions/pnf-software-upgrade-mapping.json"
+          }
+        }
+      },
+      "downloadNeSw" : {
+        "type" : "component-script-executor",
+        "interfaces" : {
+          "ComponentScriptExecutor" : {
+            "operations" : {
+              "process" : {
+                "implementation" : {
+                  "primary" : "component-script",
+                  "timeout" : 180,
+                  "operation_host" : "SELF"
+                },
+                "inputs" : {
+                  "script-type" : "kotlin",
+                  "script-class-reference" : "cba.pnf.swug.RestconfSoftwareUpgrade",
+                  "dynamic-properties" : "*downloadNeSw-properties"
+                },
+                "outputs" : {
+                  "response-data" : "",
+                  "status" : "success"
+                }
+              }
+            }
+          }
+        },
+        "artifacts" : {
+          "mount-node-template" : {
+            "type" : "artifact-template-velocity",
+            "file" : "Templates/restconf-mount-template.vtl"
+          },
+          "mount-node-mapping" : {
+            "type" : "artifact-mapping-resource",
+            "file" : "Definitions/pnf-software-upgrade-mapping.json"
+          },
+          "configure-template" : {
+            "type" : "artifact-template-velocity",
+            "file" : "Templates/pnf-swug-config-template.vtl"
+          },
+          "configure-mapping" : {
+            "type" : "artifact-mapping-resource",
+            "file" : "Definitions/pnf-software-upgrade-mapping.json"
+          },
+          "download-ne-sw-template" : {
+            "type" : "artifact-template-velocity",
+            "file" : "Templates/pnf-swug-download-ne-sw-template.vtl"
+          },
+          "download-ne-sw-mapping" : {
+            "type" : "artifact-mapping-resource",
+            "file" : "Definitions/pnf-software-upgrade-mapping.json"
+          }
+        }
+      },
+      "activateNeSw" : {
+        "type" : "component-script-executor",
+        "interfaces" : {
+          "ComponentScriptExecutor" : {
+            "operations" : {
+              "process" : {
+                "implementation" : {
+                  "primary" : "component-script",
+                  "timeout" : 180,
+                  "operation_host" : "SELF"
+                },
+                "inputs" : {
+                  "script-type" : "kotlin",
+                  "script-class-reference" : "cba.pnf.swug.RestconfSoftwareUpgrade",
+                  "dynamic-properties" : "*activateNeSw-properties"
+                },
+                "outputs" : {
+                  "response-data" : "",
+                  "status" : "success"
+                }
+              }
+            }
+          }
+        },
+        "artifacts" : {
+          "mount-node-template" : {
+            "type" : "artifact-template-velocity",
+            "file" : "Templates/restconf-mount-template.vtl"
+          },
+          "mount-node-mapping" : {
+            "type" : "artifact-mapping-resource",
+            "file" : "Definitions/pnf-software-upgrade-mapping.json"
+          },
+          "configure-template" : {
+            "type" : "artifact-template-velocity",
+            "file" : "Templates/pnf-swug-config-template.vtl"
+          },
+          "configure-mapping" : {
+            "type" : "artifact-mapping-resource",
+            "file" : "Definitions/pnf-software-upgrade-mapping.json"
+          }
+        }
+      },
+      "postcheck" : {
+        "type" : "component-script-executor",
+        "interfaces" : {
+          "ComponentScriptExecutor" : {
+            "operations" : {
+              "process" : {
+                "implementation" : {
+                  "primary" : "component-script",
+                  "timeout" : 180,
+                  "operation_host" : "SELF"
+                },
+                "inputs" : {
+                  "script-type" : "kotlin",
+                  "script-class-reference" : "cba.pnf.swug.RestconfSoftwareUpgrade",
+                  "dynamic-properties" : "*postcheck-properties"
+                },
+                "outputs" : {
+                  "response-data" : "",
+                  "status" : "success"
+                }
+              }
+            }
+          }
+        },
+        "artifacts" : {
+          "mount-node-template" : {
+            "type" : "artifact-template-velocity",
+            "file" : "Templates/restconf-mount-template.vtl"
+          },
+          "mount-node-mapping" : {
+            "type" : "artifact-mapping-resource",
+            "file" : "Definitions/pnf-software-upgrade-mapping.json"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/data_types.json b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/data_types.json
new file mode 100644 (file)
index 0000000..e3d216c
--- /dev/null
@@ -0,0 +1,75 @@
+{
+  "data_types" : {
+    "dt-config-assign-properties" : {
+      "description" : "Dynamic DataType definition for workflow(config-assign).",
+      "version" : "1.0.0",
+      "properties" : { },
+      "derived_from" : "tosca.datatypes.Dynamic"
+    },
+    "dt-config-deploy-properties" : {
+      "description" : "Dynamic DataType definition for workflow(config-deploy).",
+      "version" : "1.0.0",
+      "properties" : {
+        "pnf-ipv4-address" : {
+          "type" : "string"
+        },
+        "pnf-id" : {
+          "type" : "string"
+        }
+      },
+      "derived_from" : "tosca.datatypes.Dynamic"
+    },
+    "dt-precheck-properties": {
+      "description": "Dynamic DataType definition for the precheck workflow(upgrade-software).",
+      "version": "1.0.0",
+      "properties": {
+        "pnf-id": {
+          "type": "string"
+        },
+        "target-software-version": {
+          "type": "string"
+        }
+      },
+      "derived_from": "tosca.datatypes.Dynamic"
+    },
+    "dt-downloadNeSw-properties": {
+      "description": "Dynamic DataType definition for the downloadNeSw workflow(upgrade-software).",
+      "version": "1.0.0",
+      "properties": {
+        "pnf-id": {
+          "type": "string"
+        },
+        "target-software-version": {
+          "type": "string"
+        }
+      },
+      "derived_from": "tosca.datatypes.Dynamic"
+    },
+    "dt-activateNeSw-properties": {
+      "description": "Dynamic DataType definition for the activateNeSw workflow(upgrade-software).",
+      "version": "1.0.0",
+      "properties": {
+        "pnf-id": {
+          "type": "string"
+        },
+        "target-software-version": {
+          "type": "string"
+        }
+      },
+      "derived_from": "tosca.datatypes.Dynamic"
+    },
+    "dt-postcheck-properties": {
+      "description": "Dynamic DataType definition for the postcheck workflow(upgrade-software).",
+      "version": "1.0.0",
+      "properties": {
+        "pnf-id": {
+          "type": "string"
+        },
+        "target-software-version": {
+          "type": "string"
+        }
+      },
+      "derived_from": "tosca.datatypes.Dynamic"
+    }
+  }
+}
\ No newline at end of file
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/pnf-software-upgrade-mapping.json b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/pnf-software-upgrade-mapping.json
new file mode 100644 (file)
index 0000000..2c3de2e
--- /dev/null
@@ -0,0 +1,36 @@
+[
+  {
+    "name": "pnf-id",
+    "input-param": true,
+    "property": {
+      "type": "string"
+    },
+    "dictionary-name": "pnf-id",
+    "dictionary-source": "input",
+    "dependencies": [
+    ]
+  },
+  {
+    "name": "target-software-version",
+    "input-param": true,
+    "property": {
+      "type": "string"
+    },
+    "dictionary-name": "target-software-version",
+    "dictionary-source": "input",
+    "dependencies": [
+    ]
+  },
+  {
+    "name": "pnf-ipv4-address",
+    "input-param": false,
+    "property": {
+      "type": "string"
+    },
+    "dictionary-name": "pnf-ipaddress-aai",
+    "dictionary-source": "aai-data",
+    "dependencies": [
+      "pnf-id"
+    ]
+  }
+]
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Plans/CONFIG_upgradeSoftware.xml b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Plans/CONFIG_upgradeSoftware.xml
new file mode 100644 (file)
index 0000000..52a9900
--- /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="upgrade-software" method="process">
+                <outcome value="failure">
+                    <return status="failure"/>
+                </outcome>
+                <outcome value="success">
+                    <return status="success"/>
+                </outcome>
+            </execute>
+        </block>
+    </method>
+</service-logic>
@@ -1,6 +1,6 @@
 /*
 * ============LICENSE_START=======================================================
-*  Copyright (C) 2019 Nordix Foundation.
+*  Copyright (C) 2020 Nordix Foundation.
 * ================================================================================
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -67,9 +67,9 @@ class RestconfConfigDeploy : AbstractScriptComponentFunction() {
 
                 val jsonResult = mapper.readTree((result.body).toString())
 
-                if(jsonResult.get("ietf-yang-patch:yang-patch-status").get("errors") != null) {
+                if (jsonResult.get("ietf-yang-patch:yang-patch-status").get("errors") != null) {
                     log.error("There was an error configuring device")
-                }  else {
+                } else {
                     log.info("Device has been configured succesfully")
                 }
 
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Scripts/kotlin/RestconfSoftwareUpgrade.kt b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Scripts/kotlin/RestconfSoftwareUpgrade.kt
new file mode 100644 (file)
index 0000000..07e804b
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+* ============LICENSE_START=======================================================
+*  Copyright (C) 2020 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.
+* ============LICENSE_END=========================================================
+ */
+
+
+package cba.pnf.swug
+
+import com.fasterxml.jackson.databind.node.ObjectNode
+import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput
+import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.contentFromResolvedArtifactNB
+import org.onap.ccsdk.cds.blueprintsprocessor.functions.restconf.executor.restconfApplyDeviceConfig
+import org.onap.ccsdk.cds.blueprintsprocessor.functions.restconf.executor.restconfDeviceConfig
+import org.onap.ccsdk.cds.blueprintsprocessor.functions.restconf.executor.restconfMountDevice
+import org.onap.ccsdk.cds.blueprintsprocessor.functions.restconf.executor.restconfUnMountDevice
+import org.onap.ccsdk.cds.blueprintsprocessor.rest.restClientService
+import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService
+import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.AbstractScriptComponentFunction
+import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintException
+import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintRetryException
+import org.onap.ccsdk.cds.controllerblueprints.core.logger
+import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintDependencyService
+import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
+
+class RestconfSoftwareUpgrade : AbstractScriptComponentFunction() {
+
+    private val RESTCONF_SERVER_IDENTIFIER = "sdnc"
+    private val CONFIGLET_RESOURCE_PATH = "yang-ext:mount/pnf-sw-upgrade:software-upgrade"
+    private val log = logger(AbstractScriptComponentFunction::class.java)
+    private val TARGET_SOFTWARE_PATH = "$CONFIGLET_RESOURCE_PATH/upgrade-package/"
+
+    override suspend fun processNB(executionRequest: ExecutionServiceInput) {
+
+        // Extract request properties
+        val properties = requestPayloadActionProperty(executionRequest.actionIdentifiers.actionName + "-properties")!!.get(0)
+        val model= SoftwareUpgradeModel(getDynamicProperties("resolution-key").asText(),
+            BluePrintDependencyService.restClientService(RESTCONF_SERVER_IDENTIFIER),
+            properties.get("pnf-id").textValue(), properties.get("target-software-version").textValue(),
+            Action.getEnumFromActionName(executionRequest.actionIdentifiers.actionName))
+
+        log.info("Blueprint invoked for ${model.resolutionKey} for SW Upgrade : " +
+            "${model.action} for sw version ${model.targetSwVersion} on pnf: ${model.deviceId}")
+
+        try {
+            val mountPayload = contentFromResolvedArtifactNB("mount-node")
+            log.debug("Mount Payload : $mountPayload")
+            restconfMountDevice(model.client, model.deviceId, mountPayload, mutableMapOf("Content-Type" to "application/json"))
+
+            when (model.action) {
+                Action.PRE_CHECK -> processPrecheck(model)
+                Action.DOWNLOAD_NE_SW -> processDownloadNeSw(model)
+                Action.ACTIVATE_NE_SW -> processActivateNeSw(model)
+                Action.POST_CHECK -> processPostcheck(model)
+                Action.CANCEL -> processCancel(model)
+            }
+
+        } catch (err: Exception) {
+            log.error("an error occurred while configuring device {}", err)
+        } finally {
+            restconfUnMountDevice(model.client, model.deviceId, "")
+        }
+    }
+
+    private suspend fun processPrecheck(model: SoftwareUpgradeModel) {
+        log.debug("In PNF SW upgrade : processPreCheck")
+        //Log the current configuration for the subtree
+        val payloadObject = getCurrentConfig(model)
+        log.debug("Current sw version on pnf : ${payloadObject.get("software-upgrade")?.get("upgrade-package")?.get(0)?.get("software-version")?.asText()}")
+        log.info("PNF is Healthy!")
+    }
+
+    private suspend fun processDownloadNeSw(model: SoftwareUpgradeModel) {
+        log.debug("In PNF SW upgrade : processDownloadNeSw")
+        //Check if there is existing config for the targeted software version
+
+        var downloadConfigPayload: String
+        if (checkIfSwReadyToPerformAction(Action.PRE_CHECK, model)) {
+            downloadConfigPayload = contentFromResolvedArtifactNB("configure")
+            downloadConfigPayload =downloadConfigPayload.replace("%id%", model.yangId)
+        }
+        else {
+            downloadConfigPayload = contentFromResolvedArtifactNB("download-ne-sw")
+            model.yangId=model.targetSwVersion
+        }
+        downloadConfigPayload = downloadConfigPayload.replace("%actionName%", Action.DOWNLOAD_NE_SW.name)
+        log.info("Config Payload to start download : $downloadConfigPayload")
+
+        //Apply configlet
+        restconfApplyDeviceConfig(model.client, model.deviceId, CONFIGLET_RESOURCE_PATH, downloadConfigPayload,
+            mutableMapOf("Content-Type" to "application/yang.patch+json"))
+
+        //Poll PNF for Download action's progress
+        checkExecution(model)
+    }
+
+    private suspend fun processActivateNeSw(model: SoftwareUpgradeModel) {
+        log.debug("In PNF SW upgrade : processActivateNeSw")
+        //Check if the software is downloaded and ready to be activated
+        if (checkIfSwReadyToPerformAction(Action.DOWNLOAD_NE_SW, model)) {
+            var activateConfigPayload: String = contentFromResolvedArtifactNB("configure")
+            activateConfigPayload = activateConfigPayload.replace("%actionName%", Action.ACTIVATE_NE_SW.name)
+            log.info("Config Payload to start activate : $activateConfigPayload")
+            //Apply configlet
+            restconfApplyDeviceConfig(model.client, model.deviceId, CONFIGLET_RESOURCE_PATH, activateConfigPayload,
+                mutableMapOf("Content-Type" to "application/yang.patch+json"))
+
+            //Poll PNF for Activate action's progress
+            checkExecution(model)
+        } else {
+            throw BluePrintRetryException("Software Download not completed for device(${model.deviceId}) to activate sw version: ${model.targetSwVersion}")
+        }
+    }
+
+    private suspend fun processPostcheck(model: SoftwareUpgradeModel) {
+        log.info("In PNF SW upgrade : processPostcheck")
+        //Log the current configuration for the subtree
+        if (checkIfSwReadyToPerformAction(Action.POST_CHECK, model)) {
+            log.info("PNF is healthy post activation!")
+        }
+    }
+
+    private fun processCancel(model :SoftwareUpgradeModel) {
+        //This is for future implementation of cancel step during software upgrade
+        log.info("In PNF SW upgrade : processCancel")
+    }
+
+    private suspend fun getCurrentConfig(model: SoftwareUpgradeModel) : ObjectNode{
+        val currentConfig: BlueprintWebClientService.WebClientResponse<String> = restconfDeviceConfig(model.client, model.deviceId, CONFIGLET_RESOURCE_PATH)
+        return JacksonUtils.jsonNode(currentConfig.body) as ObjectNode
+    }
+    private suspend fun checkExecution(model: SoftwareUpgradeModel) {
+        val checkExecutionBlock: suspend (Int) -> String = {
+            val result = restconfDeviceConfig(model.client, model.deviceId, TARGET_SOFTWARE_PATH.plus(model.yangId))
+            if (result.body.contains(model.action.completionStatus)) {
+                log.info("${model.action.name} is complete")
+                result.body
+            } else {
+                throw BluePrintRetryException("Waiting for device(${model.deviceId}) to activate sw version ${model.targetSwVersion}")
+            }
+        }
+        model.client.retry<String>(10, 0, 1000, checkExecutionBlock)
+
+    }
+
+    private suspend fun checkIfSwReadyToPerformAction(action : Action, model: SoftwareUpgradeModel): Boolean {
+        val configBody = getCurrentConfig(model)
+        configBody.get("software-upgrade")?.get("upgrade-package")?.iterator()?.forEach { item ->
+            if (model.targetSwVersion == item.get("software-version")?.asText() &&
+                action.completionStatus == item?.get("current-status")?.asText()) {
+                model.yangId= item.get("id").textValue()
+                return true
+            }
+        }
+        return false
+    }
+
+    override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) {
+        log.info("Recover function called!")
+        log.info("Execution request : $executionRequest")
+        log.error("Exception", runtimeException)
+    }
+}
+
+enum class Action(val actionName: String, val completionStatus: String) {
+    PRE_CHECK("precheck", "INITIALIZED"),
+    DOWNLOAD_NE_SW("downloadNeSw", "DOWNLOAD_COMPLETED"),
+    ACTIVATE_NE_SW("activateNeSw", "ACTIVATION_COMPLETED"),
+    POST_CHECK("postcheck", "ACTIVATION_COMPLETED"),
+    CANCEL("cancel", "CANCELLED")
+    ;
+    companion object{
+        fun getEnumFromActionName(name: String): Action {
+            for(value in values()){
+                if (value.actionName==name) return value
+            }
+            throw BluePrintException("Invalid Action sent to CDS")
+        }
+    }
+}
+
+data class SoftwareUpgradeModel(val resolutionKey: String, val client: BlueprintWebClientService, val deviceId: String,
+                                val targetSwVersion: String, val action: Action, var yangId: String = "")
\ No newline at end of file
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/TOSCA-Metadata/TOSCA.meta b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/TOSCA-Metadata/TOSCA.meta
new file mode 100644 (file)
index 0000000..4679646
--- /dev/null
@@ -0,0 +1,7 @@
+TOSCA-Meta-File-Version: 1.0.0
+CSAR-Version: 1.0
+Created-By: Raj Gumma <raj.gumma@est.tech>
+Entry-Definitions: Definitions/PNF_CDS_RESTCONF.json
+Template-Name: PNF_CDS_RESTCONF
+Template-Version: 1.0.0
+Template-Tags: PNF_CDS_RESTCONF
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Templates/pnf-swug-config-template.vtl b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Templates/pnf-swug-config-template.vtl
new file mode 100644 (file)
index 0000000..5e52f67
--- /dev/null
@@ -0,0 +1,22 @@
+{
+  "ietf-restconf:yang-patch": {
+    "patch-id": "patch-1",
+    "edit": [
+      {
+        "edit-id": "edit1",
+        "operation": "merge",
+        "target": "/",
+        "value": {
+          "software-upgrade": {
+            "upgrade-package": [
+              {
+                "id": "%id%",
+                "action": "%actionName%"
+              }
+            ]
+          }
+        }
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Templates/pnf-swug-download-ne-sw-template.vtl b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Templates/pnf-swug-download-ne-sw-template.vtl
new file mode 100644 (file)
index 0000000..695b668
--- /dev/null
@@ -0,0 +1,28 @@
+{
+  "ietf-restconf:yang-patch": {
+    "patch-id": "patch-1",
+    "edit": [
+      {
+        "edit-id": "edit1",
+        "operation": "merge",
+        "target": "/",
+        "value": {
+          "software-upgrade": {
+            "upgrade-package": [
+              {
+                  "id": "${target-software-version}",
+                  "current-status": "INITIALIZED",
+                  "action": "%actionName%",
+                  "user-label": "trial software update",
+                  "uri": "sftp://127.0.0.1/test_software_2.img",
+                  "software-version": "${target-software-version}",
+                  "user": "test_user",
+                  "password": "test_password"
+              }
+            ]
+          }
+        }
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Tests/uat.yaml b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Tests/uat.yaml
new file mode 100644 (file)
index 0000000..79328e6
--- /dev/null
@@ -0,0 +1,374 @@
+%YAML 1.1
+---
+processes:
+  - name: config-assign
+    request:
+      commonHeader: &commonHeader
+        originatorId: sdnc
+        requestId: "123456-1000"
+        subRequestId: sub-123456-1000
+      actionIdentifiers: &assign-ai
+        blueprintName: PNF_CDS_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
+            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: {}
+  - name: config-deploy
+    request:
+      commonHeader: *commonHeader
+      actionIdentifiers: &deploy-ai
+        actionName: config-deploy
+        blueprintName: PNF_CDS_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
+            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
+  - name: precheck
+    request:
+      commonHeader: &swugCommonHeader
+        originatorId: sdnc
+        requestId: "123456-1000"
+        subRequestId: sub-123456-1000
+      actionIdentifiers: &precheck-ai
+        blueprintName: PNF_CDS_RESTCONF
+        blueprintVersion: "1.0.0"
+        actionName: precheck
+        mode: sync
+      payload:
+        precheck-request:
+          resolution-key: &resKey "RES-KEY 61"
+          precheck-properties: &actionProps
+            service-instance-id: siid_1234
+            pnf-id: &pnfId pnf-id-2019-07-12
+            target-software-version: "2.0.2"
+            service-model-uuid: service-model-uuid
+            pnf-customization-uuid: pnf-customization-uuid
+    expectedResponse:
+      commonHeader: *swugCommonHeader
+      actionIdentifiers: *precheck-ai
+      status:
+        code: 200
+        eventType: EVENT_COMPONENT_EXECUTED
+        errorMessage: null
+        message: success
+      payload:
+        precheck-response: {}
+  - name: downloadNeSw
+    request:
+      commonHeader: *swugCommonHeader
+      actionIdentifiers: &download-ai
+        blueprintName: PNF_CDS_RESTCONF
+        blueprintVersion: "1.0.0"
+        actionName: downloadNeSw
+        mode: sync
+      payload:
+        downloadNeSw-request:
+          resolution-key: *resKey
+          downloadNeSw-properties: *actionProps
+    expectedResponse:
+      commonHeader: *swugCommonHeader
+      actionIdentifiers: *download-ai
+      status:
+        code: 200
+        eventType: EVENT_COMPONENT_EXECUTED
+        errorMessage: null
+        message: success
+      payload:
+        downloadNeSw-response: {}
+  - name: activateNeSw
+    request:
+      commonHeader: *swugCommonHeader
+      actionIdentifiers: &activate-ai
+        blueprintName: PNF_CDS_RESTCONF
+        blueprintVersion: "1.0.0"
+        actionName: activateNeSw
+        mode: sync
+      payload:
+        activateNeSw-request:
+          resolution-key: *resKey
+          activateNeSw-properties: *actionProps
+    expectedResponse:
+      commonHeader: *swugCommonHeader
+      actionIdentifiers: *activate-ai
+      status:
+        code: 200
+        eventType: EVENT_COMPONENT_EXECUTED
+        errorMessage: null
+        message: success
+      payload:
+        activateNeSw-response: {}
+  - name: postcheck
+    request:
+      commonHeader: *swugCommonHeader
+      actionIdentifiers: &postcheck-ai
+        blueprintName: PNF_CDS_RESTCONF
+        blueprintVersion: "1.0.0"
+        actionName: postcheck
+        mode: sync
+      payload:
+        postcheck-request:
+          resolution-key: *resKey
+          postcheck-properties: *actionProps
+    expectedResponse:
+      commonHeader: *swugCommonHeader
+      actionIdentifiers: *postcheck-ai
+      status:
+        code: 200
+        eventType: EVENT_COMPONENT_EXECUTED
+        errorMessage: null
+        message: success
+      payload:
+        postcheck-response: {}
+external-services:
+  - selector: aai-data
+    expectations:
+      - request:
+          method: GET
+          path: [ /aai/v14/network/pnfs/pnf, *pnfId]
+          headers:
+            Accept: application/json
+        response:
+          headers:
+            Content-Type: application/json
+          body:
+            ipaddress-v4-oam: &pnfAddress 13.13.13.13
+            ipaddress-v6-oam: 1::13
+  - selector: sdnc
+    expectations:
+      - request:
+          method: PUT
+          path: &configUri [ /restconf/config, &nodeIdentifier [network-topology:network-topology/topology/topology-netconf/node, *pnfId]]
+          headers:
+            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]
+          headers:
+            Content-Type: application/yang.patch+json
+          body:
+            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" }]}}
+        response:
+          body:
+            ietf-yang-patch:yang-patch-status:
+              patch-id: patch-1
+              ok: [
+                null
+              ]
+      ### External expectations for Software Upgrade
+      - request:
+          method: GET
+          path: &ConfigSwUgUri [*configUri, &configletResourcePath yang-ext:mount/pnf-sw-upgrade:software-upgrade]
+          headers:
+            Accept: application/json
+        responses:
+          - headers:
+              Content-Type: application/json
+            body:
+              software-upgrade:
+                upgrade-package:
+                  - id: 2.0.1
+                    current-status: INITIALIZED
+                    user-label: trial software update
+                    uri: sftp:127.0.0.1/test_software_1.img
+                    software-version: 2.0.1
+                    user: test_user
+                    password: test_password
+          - headers:
+              Content-Type: application/json
+            body:
+              software-upgrade:
+                upgrade-package:
+                  - id: 2.0.1
+                    current-status: INITIALIZED
+                    user-label: trial software update
+                    uri: sftp:127.0.0.1/test_software_1.img
+                    software-version: 2.0.1
+                    user: test_user
+                    password: test_password
+          - headers:
+              Content-Type: application/json
+            body:
+              software-upgrade:
+                upgrade-package:
+                  - id: 2.0.1
+                    current-status: INITIALIZED
+                    user-label: trial software update
+                    uri: sftp:127.0.0.1/test_software_1.img
+                    software-version: 2.0.1
+                    user: test_user
+                    password: test_password
+                  - id: 2.0.2
+                    current-status: DOWNLOAD_COMPLETED
+                    state-change-time: '2020-02-20T13:03:21Z'
+                    software-version: 2.0.2
+                    user-label: trial software update
+                    uri: sftp:127.0.0.1/test_software_1.img
+                    user: test_user
+                    password: test_password
+          - headers:
+              Content-Type: application/json
+            body:
+              software-upgrade:
+                upgrade-package:
+                  - id: 2.0.1
+                    current-status: INITIALIZED
+                    user-label: trial software update
+                    uri: sftp:127.0.0.1/test_software_1.img
+                    software-version: 2.0.1
+                    user: test_user
+                    password: test_password
+                  - id: 2.0.2
+                    current-status: ACTIVATION_COMPLETED
+                    state-change-time: '2020-02-20T13:03:21Z'
+                    software-version: 2.0.2
+                    user-label: trial software update
+                    uri: sftp:127.0.0.1/test_software_1.img
+                    user: test_user
+                    password: test_password
+      - request:
+          method: PATCH
+          path: *ConfigSwUgUri
+          headers:
+            Content-Type: application/yang.patch+json
+          body:
+            ietf-restconf:yang-patch:
+              patch-id: patch-1
+              edit:
+                - edit-id: edit1
+                  operation: merge
+                  target: "/"
+        response:
+          headers:
+            Content-Type: application/yang.patch-status+json
+          body:
+            { ietf-yang-patch:yang-patch-status: {patch-id: patch-1, ok: [ ] } }
+        times: 2
+      - request:
+          method: GET
+          path: [*ConfigSwUgUri, upgrade-package/2.0.2]
+          headers:
+            Accept: application/json
+        responses:
+          - headers:
+              Content-Type: application/json
+            body:
+              upgrade-package:
+                - id: 2.0.2
+                  current-status: DOWNLOAD_IN_PROGRESS
+                  state-change-time: '2020-02-20T12:17:34.984Z'
+                  software-version: 2.0.2
+          - headers:
+              Content-Type: application/json
+            body:
+              upgrade-package:
+                - id: 2.0.2
+                  current-status: DOWNLOAD_IN_PROGRESS
+                  state-change-time: '2020-02-20T12:52:30Z'
+                  software-version: 2.0.2
+          - headers:
+              Content-Type: application/json
+            body:
+              upgrade-package:
+                - id: 2.0.2
+                  current-status: DOWNLOAD_COMPLETED
+                  state-change-time: '2020-02-20T13:03:21Z'
+                  software-version: 2.0.2
+          - headers:
+              Content-Type: application/json
+            body:
+              upgrade-package:
+                - id: 2.0.2
+                  current-status: ACTIVATION_IN_PROGRESS
+                  state-change-time: '2020-02-20T13:05:08Z'
+                  software-version: 2.0.2
+          - headers:
+              Content-Type: application/json
+            body:
+              upgrade-package:
+                - id: 2.0.2
+                  current-status: ACTIVATION_IN_PROGRESS
+                  state-change-time: '2020-02-20T12:52:30Z'
+                  software-version: 2.0.2
+          - headers:
+              Content-Type: application/json
+            body:
+              upgrade-package:
+                - id: 2.0.2
+                  current-status: ACTIVATION_COMPLETED
+                  state-change-time: '2020-02-20T13:07:12Z'
+                  software-version: 2.0.2
+      - request:
+          method: DELETE
+          path: *configUri
+        times: 5
\ No newline at end of file
index 56cb329..ffbc15a 100644 (file)
@@ -61,7 +61,8 @@ message Uat {
 
     message Expectation {
         required Request request = 1;
-        required Response response = 2;
+        optional Response response = 2;
+        repeated Response responses = 3;
     }
 
     message ExternalService {
index 85b10c6..a58d089 100644 (file)
@@ -64,7 +64,7 @@ external-services:
     expectations:
       - request:
           method: PUT
-          path: &configUri [ restconf/config, &nodeIdentifier [network-topology:network-topology/topology/topology-netconf/node, *pnfId]]
+          path: &configUri [ /restconf/config, &nodeIdentifier [network-topology:network-topology/topology/topology-netconf/node, *pnfId]]
           headers:
             Content-Type: application/json
           body:
@@ -82,7 +82,7 @@ external-services:
           status: 201
       - request:
           method: GET
-          path: [ restconf/operational, *nodeIdentifier]
+          path: [ /restconf/operational, *nodeIdentifier]
         response:
           body:
             node: [ { netconf-node-topology:connection-status: connected }]
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/data_types.json b/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/data_types.json
deleted file mode 100644 (file)
index a0804bb..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-{
-  "data_types" : {
-    "dt-config-assign-properties" : {
-      "description" : "Dynamic DataType definition for workflow(config-assign).",
-      "version" : "1.0.0",
-      "properties" : { },
-      "derived_from" : "tosca.datatypes.Dynamic"
-    },
-    "dt-config-deploy-properties" : {
-      "description" : "Dynamic DataType definition for workflow(config-deploy).",
-      "version" : "1.0.0",
-      "properties" : {
-        "pnf-ipv4-address" : {
-          "type" : "string"
-        },
-        "pnf-id" : {
-          "type" : "string"
-        }
-      },
-      "derived_from" : "tosca.datatypes.Dynamic"
-    }
-  }
-}
\ No newline at end of file
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/pnf_config_aai.json b/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/pnf_config_aai.json
deleted file mode 100644 (file)
index 3ef585c..0000000
+++ /dev/null
@@ -1,152 +0,0 @@
-{
-  "tosca_definitions_version" : "controller_blueprint_1_0_0",
-  "metadata" : {
-    "template_author" : "Rahul Tyagi",
-    "author-email" : "rahul.tyagi@est.tech",
-    "user-groups" : "ADMIN, OPERATION",
-    "template_name" : "pnf_config_aai",
-    "template_version" : "1.0.0",
-    "template_tags" : "pnf, restconf, config, configuration"
-  },
-  "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"
-  } ],
-  "dsl_definitions" : {
-    "config-deploy-properties" : {
-      "resolution-key" : {
-        "get_input" : "resolution-key"
-      }
-    }
-  },
-  "topology_template" : {
-    "workflows" : {
-      "config-assign" : {
-        "steps" : {
-          "activate-process" : {
-            "description" : "Create a configlet",
-            "target" : "config-assign",
-            "activities" : [ {
-              "call_operation" : ""
-            } ]
-          }
-        },
-        "inputs" : {
-          "resolution-key" : {
-            "required" : true,
-            "type" : "string"
-          },
-          "store-result" : {
-            "required" : true,
-            "type" : "boolean"
-          },
-          "config-assign-properties" : {
-            "description" : "Dynamic PropertyDefinition for workflow(config-assign).",
-            "required" : true,
-            "type" : "dt-config-assign-properties"
-          }
-        }
-      },
-      "config-deploy" : {
-        "steps" : {
-          "activate-process" : {
-            "description" : "Send a configlet to the pnf",
-            "target" : "config-deploy",
-            "activities" : [ {
-              "call_operation" : ""
-            } ]
-          }
-        },
-        "inputs" : {
-          "resolution-key" : {
-            "required" : true,
-            "type" : "string"
-          },
-          "config-deploy-properties" : {
-            "description" : "Dynamic PropertyDefinition for workflow(config-deploy).",
-            "required" : true,
-            "type" : "dt-config-deploy-properties"
-          }
-        }
-      }
-    },
-    "node_templates" : {
-      "config-assign" : {
-        "type" : "component-resource-resolution",
-        "interfaces" : {
-          "ResourceResolutionComponent" : {
-            "operations" : {
-              "process" : {
-                "inputs" : {
-                  "resolution-key" : {
-                    "get_input" : "resolution-key"
-                  },
-                  "store-result" : true,
-                  "artifact-prefix-names" : [ "config-assign" ]
-                },
-                "outputs" : {
-                  "resource-assignment-params" : {
-                    "get_attribute" : [ "SELF", "assignment-params" ]
-                  },
-                  "status" : "success"
-                }
-              }
-            }
-          }
-        },
-        "artifacts" : {
-          "config-assign-template" : {
-            "type" : "artifact-template-velocity",
-            "file" : "Templates/config-assign-restconf-configlet-template.vtl"
-          },
-          "config-assign-mapping" : {
-            "type" : "artifact-mapping-resource",
-            "file" : "Definitions/config-assign-pnf-mapping.json"
-          }
-        }
-      },
-      "config-deploy" : {
-        "type" : "component-script-executor",
-        "interfaces" : {
-          "ComponentScriptExecutor" : {
-            "operations" : {
-              "process" : {
-                "implementation" : {
-                  "primary" : "component-script",
-                  "timeout" : 180,
-                  "operation_host" : "SELF"
-                },
-                "inputs" : {
-                  "script-type" : "kotlin",
-                  "script-class-reference" : "cba.pnf.config.aai.RestconfConfigDeploy",
-                  "dynamic-properties" : "*config-deploy-properties"
-                },
-                "outputs" : {
-                  "response-data" : "",
-                  "status" : "success"
-                }
-              }
-            }
-          }
-        },
-        "artifacts" : {
-          "config-deploy-template" : {
-            "type" : "artifact-template-velocity",
-            "file" : "Templates/config-deploy-restconf-mount-template.vtl"
-          },
-          "config-deploy-mapping" : {
-            "type" : "artifact-mapping-resource",
-            "file" : "Definitions/config-deploy-pnf-mapping.json"
-          }
-        }
-      }
-    }
-  }
-}
\ No newline at end of file
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/TOSCA-Metadata/TOSCA.meta b/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/TOSCA-Metadata/TOSCA.meta
deleted file mode 100644 (file)
index 9036008..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-TOSCA-Meta-File-Version: 1.0.0
-CSAR-Version: 1.0
-Created-By: Rahul Tyagi
-Entry-Definitions: Definitions/pnf_config_aai.json
-Template-Name: pnf_config_aai
-Template-Version: 1.0.0
-Template-Tags: pnf_config_aai
diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Tests/uat.yaml b/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Tests/uat.yaml
deleted file mode 100644 (file)
index 13e10f3..0000000
+++ /dev/null
@@ -1,137 +0,0 @@
-%YAML 1.1
----
-processes:
-  - name: config-assign
-    request:
-      commonHeader: &commonHeader
-        originatorId: sdnc
-        requestId: "123456-1000"
-        subRequestId: sub-123456-1000
-      actionIdentifiers: &assign-ai
-        blueprintName: pnf_config_aai
-        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
-            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: {}
-  - name: config-deploy
-    request:
-      commonHeader: *commonHeader
-      actionIdentifiers: &deploy-ai
-        actionName: config-deploy
-        blueprintName: pnf_config_aai
-        blueprintVersion: "1.0.0"
-        mode: sync
-      payload:
-        config-deploy-request:
-          resolution-key: *resKey
-          config-deploy-properties:
-            service-instance-id: siid_1234
-            pnf-id: *pnfId
-            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
-
-external-services:
-  - selector: aai-data
-    expectations:
-      - request:
-          method: GET
-          path: [ /aai/v14/network/pnfs/pnf, *pnfId]
-          headers:
-            Accept: application/json
-        response:
-          headers:
-            Content-Type: application/json
-          body:
-            ipaddress-v4-oam: &pnfAddress 13.13.13.13
-            ipaddress-v6-oam: 1::13
-  - selector: sdnc
-    expectations:
-      - request:
-          method: PUT
-          path: &configUri [ restconf/config, &nodeIdentifier [network-topology:network-topology/topology/topology-netconf/node, *pnfId]]
-          headers:
-            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]
-          headers:
-            Content-Type: application/yang.patch+json
-          body:
-            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" }]}}
-        response:
-          body:
-            ietf-yang-patch:yang-patch-status:
-              patch-id: patch-1
-              ok: [
-                null
-              ]
-      - request:
-          method: DELETE
-          path: *configUri
-
-
index de2cf4e..f284332 100644 (file)
@@ -38,10 +38,10 @@ blueprintsprocessor:
     remoteScriptCommand:
         enabled: true
     restclient:
-        sdncodl:
+        sdnc:
             password: Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U
             type: basic-auth
-            url: http://localhost:8282/
+            url: http://localhost:8282
             username: admin
     restconfEnabled: true
 controllerblueprints:
index aebda8c..4e7d4ce 100644 (file)
@@ -30,6 +30,8 @@ import com.github.tomakehurst.wiremock.client.WireMock.equalToJson
 import com.github.tomakehurst.wiremock.client.WireMock.request
 import com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo
 import com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig
+import com.github.tomakehurst.wiremock.http.HttpHeader
+import com.github.tomakehurst.wiremock.http.HttpHeaders
 import org.apache.http.HttpStatus
 import org.apache.http.client.methods.HttpPost
 import org.apache.http.entity.ContentType
@@ -55,8 +57,6 @@ import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.boot.web.server.LocalServerPort
 import org.springframework.core.env.ConfigurableEnvironment
 import org.springframework.core.env.MapPropertySource
-import org.springframework.http.HttpHeaders
-import org.springframework.http.MediaType
 import org.springframework.test.context.ActiveProfiles
 import org.springframework.test.context.support.TestPropertySourceUtils.INLINED_PROPERTIES_PROPERTY_SOURCE_NAME
 import org.yaml.snakeyaml.Yaml
@@ -211,7 +211,6 @@ class UatServicesTest : BaseUatTest() {
         service.expectations.forEach { expectation ->
 
             val request = expectation.request
-            val response = expectation.response
             // WebTestClient always use absolute path, prefixing with "/" if necessary
             val urlPattern = urlEqualTo(request.path.prefixIfNot("/"))
             val mappingBuilder: MappingBuilder = request(request.method, urlPattern)
@@ -222,15 +221,19 @@ class UatServicesTest : BaseUatTest() {
                 mappingBuilder.withRequestBody(equalToJson(mapper.writeValueAsString(request.body), true, true))
             }
 
-            val responseDefinitionBuilder: ResponseDefinitionBuilder = aResponse()
-                .withStatus(response.status)
-            if (response.body != null) {
-                responseDefinitionBuilder.withBody(mapper.writeValueAsBytes(response.body))
-                    .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
+            for (response in expectation.responses) {
+                val responseDefinitionBuilder: ResponseDefinitionBuilder = aResponse()
+                        .withStatus(response.status)
+                if (response.body != null) {
+                    responseDefinitionBuilder.withBody(mapper.writeValueAsBytes(response.body))
+                        .withHeaders(HttpHeaders(
+                            response.headers.entries.map { e -> HttpHeader(e.key, e.value) }))
+                }
+
+                // TODO: MockServer verification for multiple responses should be done using Wiremock scenarios
+                mappingBuilder.willReturn(responseDefinitionBuilder)
             }
 
-            mappingBuilder.willReturn(responseDefinitionBuilder)
-
             mockServer.stubFor(mappingBuilder)
         }
         return mockServer
diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/InvalidUatDefinition.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/InvalidUatDefinition.kt
new file mode 100644 (file)
index 0000000..4a75641
--- /dev/null
@@ -0,0 +1,22 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2020 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.uat.utils
+
+class InvalidUatDefinition(message: String) : RuntimeException(message)
index c45ac45..17b79f5 100644 (file)
@@ -47,18 +47,30 @@ data class RequestDefinition(
 )
 
 @JsonInclude(JsonInclude.Include.NON_EMPTY)
-data class ResponseDefinition(val status: Int = 200, val body: JsonNode? = null) {
+data class ResponseDefinition(val status: Int = 200, val body: JsonNode? = null, val headers: Map<String, String> = mapOf("Content-Type" to "application/json")) {
 
     companion object {
-        val DEFAULT_RESPONSE = ResponseDefinition()
+        val DEFAULT_RESPONSES = listOf(ResponseDefinition())
     }
 }
 
 @JsonInclude(JsonInclude.Include.NON_EMPTY)
-data class ExpectationDefinition(
+class ExpectationDefinition(
     val request: RequestDefinition,
-    val response: ResponseDefinition = ResponseDefinition.DEFAULT_RESPONSE
-)
+    response: ResponseDefinition?,
+    responses: List<ResponseDefinition>? = null,
+    val times: String = ">= 1"
+) {
+    val responses: List<ResponseDefinition> = resolveOneOrMany(response, responses, ResponseDefinition.DEFAULT_RESPONSES)
+
+    companion object {
+        fun <T> resolveOneOrMany(one: T?, many: List<T>?, defaultMany: List<T>): List<T> = when {
+            many != null -> many
+            one != null -> listOf(one)
+            else -> defaultMany
+        }
+    }
+}
 
 @JsonInclude(JsonInclude.Include.NON_EMPTY)
 data class ServiceDefinition(val selector: String, val expectations: List<ExpectationDefinition>)
@@ -97,6 +109,6 @@ data class UatDefinition(
 
     companion object {
         fun load(mapper: ObjectMapper, spec: String): UatDefinition =
-            mapper.convertValue(Yaml().load(spec), UatDefinition::class.java)
+                mapper.convertValue(Yaml().load(spec), UatDefinition::class.java)
     }
 }
index a904fa9..d120e71 100644 (file)
@@ -24,9 +24,10 @@ 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.atMost
 import com.nhaarman.mockitokotlin2.eq
 import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.times
 import com.nhaarman.mockitokotlin2.verify
 import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
 import com.nhaarman.mockitokotlin2.whenever
@@ -44,6 +45,7 @@ import org.hamcrest.CoreMatchers.equalTo
 import org.hamcrest.CoreMatchers.notNullValue
 import org.hamcrest.MatcherAssert.assertThat
 import org.mockito.Answers
+import org.mockito.verification.VerificationMode
 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
@@ -77,7 +79,8 @@ class UatExecutor(
 
     companion object {
         private const val NOOP_PASSWORD_PREFIX = "{noop}"
-
+        private const val PROPERTY_IN_UAT = "IN_UAT"
+        private val TIMES_SPEC_REGEX = "([<>]=?)?\\s*(\\d+)".toRegex()
         private val log: Logger = LoggerFactory.getLogger(UatExecutor::class.java)
         private val mockLoggingListener = MockInvocationLogger(markerOf(COLOR_MOCKITO))
     }
@@ -103,23 +106,24 @@ class UatExecutor(
     fun execute(uat: UatDefinition, cbaBytes: ByteArray): UatDefinition {
         val defaultHeaders = listOf(BasicHeader(HttpHeaders.AUTHORIZATION, clientAuthToken()))
         val httpClient = HttpClientBuilder.create()
-            .setDefaultHeaders(defaultHeaders)
-            .build()
+                .setDefaultHeaders(defaultHeaders)
+                .build()
         // Only if externalServices are defined
         val mockInterceptor = MockPreInterceptor()
         // Always defined and used, whatever the case
         val spyInterceptor = SpyPostInterceptor(mapper)
         restClientFactory.setInterceptors(mockInterceptor, spyInterceptor)
         try {
-            // Configure mocked external services and save their expected requests for further validation
-            val requestsPerClient = uat.externalServices.associateBy(
-                { service ->
-                    createRestClientMock(service.expectations).also { restClient ->
-                        // side-effect: register restClient to override real instance
-                        mockInterceptor.registerMock(service.selector, restClient)
-                    }
-                },
-                { service -> service.expectations.map { it.request } }
+            markUatBegin()
+            // Configure mocked external services and save their expectations for further validation
+            val expectationsPerClient = uat.externalServices.associateBy(
+                    { service ->
+                        createRestClientMock(service.expectations).also { restClient ->
+                            // side-effect: register restClient to override real instance
+                            mockInterceptor.registerMock(service.selector, restClient)
+                        }
+                    },
+                    { service -> service.expectations }
             )
 
             val newProcesses = httpClient.use { client ->
@@ -130,26 +134,27 @@ class UatExecutor(
                     log.info("Executing process '${process.name}'")
                     val responseNormalizer = JsonNormalizer.getNormalizer(mapper, process.responseNormalizerSpec)
                     val actualResponse = processBlueprint(
-                        client, process.request,
-                        process.expectedResponse, responseNormalizer
+                            client, process.request,
+                            process.expectedResponse, responseNormalizer
                     )
                     ProcessDefinition(
-                        process.name,
-                        process.request,
-                        actualResponse,
-                        process.responseNormalizerSpec
+                            process.name,
+                            process.request,
+                            actualResponse,
+                            process.responseNormalizerSpec
                     )
                 }
             }
 
             // Validate requests to external services
-            for ((mockClient, requests) in requestsPerClient) {
-                requests.forEach { request ->
-                    verify(mockClient, atLeastOnce()).exchangeResource(
-                        eq(request.method),
-                        eq(request.path),
-                        argThat { assertJsonEquals(request.body, this) },
-                        argThat(RequiredMapEntriesMatcher(request.headers))
+            for ((mockClient, expectations) in expectationsPerClient) {
+                expectations.forEach { expectation ->
+                    val request = expectation.request
+                    verify(mockClient, evalVerificationMode(expectation.times)).exchangeResource(
+                            eq(request.method),
+                            eq(request.path),
+                            argThat { assertJsonEquals(request.body, this) },
+                            argThat(RequiredMapEntriesMatcher(request.headers))
                     )
                 }
                 // Don't mind the invocations to the overloaded exchangeResource(String, String, String)
@@ -158,40 +163,51 @@ class UatExecutor(
             }
 
             val newExternalServices = spyInterceptor.getSpies()
-                .map(SpyService::asServiceDefinition)
+                    .map(SpyService::asServiceDefinition)
 
             return UatDefinition(newProcesses, newExternalServices)
         } finally {
             restClientFactory.clearInterceptors()
+            markUatEnd()
         }
     }
 
+    private fun markUatBegin() {
+        System.setProperty(PROPERTY_IN_UAT, "1")
+    }
+
+    private fun markUatEnd() {
+        System.clearProperty(PROPERTY_IN_UAT)
+    }
+
     private fun createRestClientMock(restExpectations: List<ExpectationDefinition>):
             BlueprintWebClientService {
         val restClient = mock<BlueprintWebClientService>(
-            defaultAnswer = Answers.RETURNS_SMART_NULLS,
-            // our custom verboseLogging handler
-            invocationListeners = arrayOf(mockLoggingListener)
+                defaultAnswer = Answers.RETURNS_SMART_NULLS,
+                // our custom verboseLogging handler
+                invocationListeners = arrayOf(mockLoggingListener)
         )
 
         // 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())
-            }
+                .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.request.method),
-                    eq(expectation.request.path),
-                    any(),
-                    any()
-                )
+            var stubbing = whenever(
+                    restClient.exchangeResource(
+                            eq(expectation.request.method),
+                            eq(expectation.request.path),
+                            any(),
+                            any()
+                    )
             )
-                .thenReturn(WebClientResponse(expectation.response.status, expectation.response.body.toString()))
+            for (response in expectation.responses) {
+                stubbing = stubbing.thenReturn(WebClientResponse(response.status, response.body.toString()))
+            }
         }
         return restClient
     }
@@ -199,9 +215,9 @@ class UatExecutor(
     @Throws(AssertionError::class)
     private fun uploadBlueprint(client: HttpClient, cbaBytes: ByteArray) {
         val multipartEntity = MultipartEntityBuilder.create()
-            .setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
-            .addBinaryBody("file", cbaBytes, ContentType.DEFAULT_BINARY, "cba.zip")
-            .build()
+                .setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
+                .addBinaryBody("file", cbaBytes, ContentType.DEFAULT_BINARY, "cba.zip")
+                .build()
         val request = HttpPost("$baseUrl/api/v1/blueprint-model/publish").apply {
             entity = multipartEntity
         }
@@ -236,6 +252,19 @@ class UatExecutor(
         return mapper.readTree(actualResponse)!!
     }
 
+    private fun evalVerificationMode(times: String): VerificationMode {
+        val matchResult = TIMES_SPEC_REGEX.matchEntire(times) ?: throw InvalidUatDefinition(
+                "Time specification '$times' does not follow expected format $TIMES_SPEC_REGEX")
+        val counter = matchResult.groups[2]!!.value.toInt()
+        return when (matchResult.groups[1]?.value) {
+            ">=" -> atLeast(counter)
+            ">" -> atLeast(counter + 1)
+            "<=" -> atMost(counter)
+            "<" -> atMost(counter - 1)
+            else -> times(counter)
+        }
+    }
+
     @Throws(AssertionError::class)
     private fun assertJsonEquals(expected: JsonNode?, actual: String): Boolean {
         // special case
@@ -249,15 +278,15 @@ class UatExecutor(
     }
 
     private fun localServerPort(): Int =
-        (environment.getProperty("local.server.port")
-            ?: environment.getRequiredProperty("blueprint.httpPort")).toInt()
+            (environment.getProperty("local.server.port")
+                    ?: environment.getRequiredProperty("blueprint.httpPort")).toInt()
 
     private fun clientAuthToken(): String {
         val username = environment.getRequiredProperty("security.user.name")
         val password = environment.getRequiredProperty("security.user.password")
         val plainPassword = when {
             password.startsWith(NOOP_PASSWORD_PREFIX) -> password.substring(
-                NOOP_PASSWORD_PREFIX.length)
+                    NOOP_PASSWORD_PREFIX.length)
             else -> username
         }
         return "Basic " + Base64Utils.encodeToString("$username:$plainPassword".toByteArray())
@@ -271,7 +300,7 @@ class UatExecutor(
         }
 
         override fun getInstance(selector: String): BlueprintWebClientService? =
-            mocks[selector]
+                mocks[selector]
 
         fun registerMock(selector: String, client: BlueprintWebClientService) {
             mocks[selector] = client
@@ -293,7 +322,7 @@ class UatExecutor(
         }
 
         fun getSpies(): List<SpyService> =
-            spies.values.toList()
+                spies.values.toList()
     }
 
     private class SpyService(
@@ -301,14 +330,14 @@ class UatExecutor(
         val selector: String,
         private val realService: BlueprintWebClientService
     ) :
-        BlueprintWebClientService by realService {
+            BlueprintWebClientService by realService {
 
         private val expectations: MutableList<ExpectationDefinition> = mutableListOf()
 
         override fun exchangeResource(methodType: String, path: String, request: String): WebClientResponse<String> =
-            exchangeResource(methodType, path, request,
-                DEFAULT_HEADERS
-            )
+                exchangeResource(methodType, path, request,
+                        DEFAULT_HEADERS
+                )
 
         override fun exchangeResource(
             methodType: String,
@@ -317,7 +346,7 @@ class UatExecutor(
             headers: Map<String, String>
         ): WebClientResponse<String> {
             val requestDefinition =
-                RequestDefinition(methodType, path, headers, toJson(request))
+                    RequestDefinition(methodType, path, headers, toJson(request))
             val realAnswer = realService.exchangeResource(methodType, path, request, headers)
             val responseBody = when {
                 // TODO: confirm if we need to normalize the response here
@@ -325,12 +354,12 @@ class UatExecutor(
                 else -> null
             }
             val responseDefinition =
-                ResponseDefinition(realAnswer.status, responseBody)
+                    ResponseDefinition(realAnswer.status, responseBody)
             expectations.add(
-                ExpectationDefinition(
-                    requestDefinition,
-                    responseDefinition
-                )
+                    ExpectationDefinition(
+                            requestDefinition,
+                            responseDefinition
+                    )
             )
             return realAnswer
         }
@@ -340,7 +369,7 @@ class UatExecutor(
         }
 
         fun asServiceDefinition() =
-            ServiceDefinition(selector, expectations)
+                ServiceDefinition(selector, expectations)
 
         private fun toJson(str: String): JsonNode? {
             return when {
@@ -351,8 +380,8 @@ class UatExecutor(
 
         companion object {
             private val DEFAULT_HEADERS = mapOf(
-                HttpHeaders.CONTENT_TYPE to MediaType.APPLICATION_JSON_VALUE,
-                HttpHeaders.ACCEPT to MediaType.APPLICATION_JSON_VALUE
+                    HttpHeaders.CONTENT_TYPE to MediaType.APPLICATION_JSON_VALUE,
+                    HttpHeaders.ACCEPT to MediaType.APPLICATION_JSON_VALUE
             )
         }
     }
index fc6d5a9..408eaf4 100644 (file)
@@ -45,12 +45,12 @@ suspend fun AbstractScriptComponentFunction.restconfMountDevice(
     headers: Map<String, String> = mutableMapOf("Content-Type" to "application/xml")
 ) {
 
-    val mountUrl = "restconf/config/network-topology:network-topology/topology/topology-netconf/node/$deviceId"
+    val mountUrl = "/restconf/config/network-topology:network-topology/topology/topology-netconf/node/$deviceId"
     log.info("sending mount request, url: $mountUrl")
     webClientService.exchangeResource("PUT", mountUrl, payload as String, headers)
 
     /** Check device has mounted */
-    val mountCheckUrl = "restconf/operational/network-topology:network-topology/topology/topology-netconf/node/$deviceId"
+    val mountCheckUrl = "/restconf/operational/network-topology:network-topology/topology/topology-netconf/node/$deviceId"
 
     val expectedResult = """"netconf-node-topology:connection-status":"connected""""
     val mountCheckExecutionBlock: suspend (Int) -> String = { tryCount: Int ->
@@ -80,7 +80,7 @@ suspend fun AbstractScriptComponentFunction.restconfApplyDeviceConfig(
 ): BlueprintWebClientService.WebClientResponse<String> {
     log.debug("headers: $additionalHeaders")
     log.info("configuring device: $deviceId, Configlet: $configletToApply")
-    val applyConfigUrl = "restconf/config/network-topology:network-topology/topology/topology-netconf/node/" +
+    val applyConfigUrl = "/restconf/config/network-topology:network-topology/topology/topology-netconf/node/" +
             "$deviceId/$configletResourcePath"
     return webClientService.exchangeResource("PATCH", applyConfigUrl, configletToApply as String, additionalHeaders)
 }
@@ -92,7 +92,7 @@ suspend fun AbstractScriptComponentFunction.restconfDeviceConfig(
 ):
         BlueprintWebClientService.WebClientResponse<String> {
 
-    val configPathUrl = "restconf/config/network-topology:network-topology/topology/topology-netconf/node/" +
+    val configPathUrl = "/restconf/config/network-topology:network-topology/topology/topology-netconf/node/" +
             "$deviceId/$configletResourcePath"
     log.debug("sending GET request,  url: $configPathUrl")
     return webClientService.exchangeResource("GET", configPathUrl, "")
@@ -106,7 +106,7 @@ suspend fun AbstractScriptComponentFunction.restconfUnMountDevice(
     deviceId: String,
     payload: String
 ) {
-    val unMountUrl = "restconf/config/network-topology:network-topology/topology/topology-netconf/node/$deviceId"
+    val unMountUrl = "/restconf/config/network-topology:network-topology/topology/topology-netconf/node/$deviceId"
     log.info("sending unMount request, url: $unMountUrl")
     webClientService.exchangeResource("DELETE", unMountUrl, "")
 }
index 6be3334..a58c077 100644 (file)
@@ -243,8 +243,11 @@ open class ClusterLockImpl(private val hazelcast: HazelcastInstance, private val
     }
 
     override suspend fun unLock() {
-        distributedLock.unlock()
-        log.trace("Cluster unlock(${name()}) successfully..")
+        // Added condition to avoid failures like - "Current thread is not owner of the lock!"
+        if (distributedLock.isLockedByCurrentThread) {
+            distributedLock.unlock()
+            log.trace("Cluster unlock(${name()}) successfully..")
+        }
     }
 
     override fun isLocked(): Boolean {
index 540b3d9..bfc0a5c 100644 (file)
@@ -20,6 +20,7 @@ import org.apache.http.message.BasicHeader
 import org.onap.ccsdk.cds.blueprintsprocessor.rest.BasicAuthRestClientProperties
 import org.springframework.http.HttpHeaders
 import org.springframework.http.MediaType
+import java.net.URI
 import java.nio.charset.Charset
 import java.util.Base64
 
@@ -43,7 +44,8 @@ class BasicAuthRestClientService(
     }
 
     override fun host(uri: String): String {
-        return restClientProperties.url + uri
+        val uri: URI = URI.create(restClientProperties.url + uri)
+        return uri.resolve(uri).toString()
     }
 
     override fun convertToBasicHeaders(headers: Map<String, String>):