From 4045d5fd60013534f4ecf85f553ae7da3e32220d Mon Sep 17 00:00:00 2001 From: "saul.gill" Date: Fri, 23 Jul 2021 10:48:25 +0100 Subject: Added endpoint for common or instance properties Runtime-controlloop and Camel endpoionts added Flag common can be used - true common props - false - instance props Changed getServiceTemplate endpoint to return less Added creation of controlloop db to clamp scripts Issue-ID: POLICY-3439 Change-Id: I9d189ca030868b47b46a2e0bc5e731c23fba2a61 Signed-off-by: saul.gill --- .../commissioning/CommissioningProvider.java | 202 ++++++++++++++++++++- .../runtime/main/rest/CommissioningController.java | 79 +++++++- runtime/extra/sql/bulkload/create-db.sql | 6 +- .../resources/clds/camel/rest/clamp-api-v2.xml | 35 ++++ .../clds/camel/routes/controlloop-flows.xml | 28 +++ .../RuntimeCommissioningResponseTestItCase.java | 47 +++++ .../http-cache/example/node_template.json | 44 +++++ .../test/resources/http-cache/third_party_proxy.py | 25 ++- 8 files changed, 446 insertions(+), 20 deletions(-) create mode 100644 runtime/src/test/resources/http-cache/example/node_template.json diff --git a/runtime-controlloop/src/main/java/org/onap/policy/clamp/controlloop/runtime/commissioning/CommissioningProvider.java b/runtime-controlloop/src/main/java/org/onap/policy/clamp/controlloop/runtime/commissioning/CommissioningProvider.java index d9dee50bc..891d42072 100644 --- a/runtime-controlloop/src/main/java/org/onap/policy/clamp/controlloop/runtime/commissioning/CommissioningProvider.java +++ b/runtime-controlloop/src/main/java/org/onap/policy/clamp/controlloop/runtime/commissioning/CommissioningProvider.java @@ -23,10 +23,10 @@ package org.onap.policy.clamp.controlloop.runtime.commissioning; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.module.jsonSchema.JsonSchema; import com.fasterxml.jackson.module.jsonSchema.factories.SchemaFactoryWrapper; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -42,6 +42,7 @@ import org.onap.policy.models.tosca.authorative.concepts.ToscaDataType; import org.onap.policy.models.tosca.authorative.concepts.ToscaNodeTemplate; import org.onap.policy.models.tosca.authorative.concepts.ToscaNodeType; import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyType; +import org.onap.policy.models.tosca.authorative.concepts.ToscaProperty; import org.onap.policy.models.tosca.authorative.concepts.ToscaRelationshipType; import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate; import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplates; @@ -180,6 +181,171 @@ public class CommissioningProvider { return controlLoopElementList; } + /** + * Get the initial node types with common or instance properties. + * + * @param fullNodeTypes map of all the node types in the specified template + * @param common boolean to indicate whether common or instance properties are required + * @return node types map that only has common properties + * @throws PfModelException on errors getting node type with common properties + */ + private Map getInitialNodeTypesMap( + Map fullNodeTypes, boolean common) { + + var tempNodeTypesMap = new HashMap(); + + fullNodeTypes.forEach((key, nodeType) -> { + var tempToscaNodeType = new ToscaNodeType(); + tempToscaNodeType.setName(key); + + var resultantPropertyMap = findCommonOrInstancePropsInNodeTypes( + nodeType, common); + + if (!resultantPropertyMap.isEmpty()) { + tempToscaNodeType.setProperties(resultantPropertyMap); + tempNodeTypesMap.put(key, tempToscaNodeType); + } + }); + return tempNodeTypesMap; + } + + private Map findCommonOrInstancePropsInNodeTypes( + ToscaNodeType nodeType, boolean common) { + + var tempCommonPropertyMap = new HashMap(); + var tempInstancePropertyMap = new HashMap(); + + nodeType.getProperties().forEach((propKey, prop) -> { + + if (prop.getMetadata() != null) { + prop.getMetadata().forEach((k, v) -> { + if (k.equals("common") && v.equals("true") && common) { + tempCommonPropertyMap.put(propKey, prop); + } else if (k.equals("common") && v.equals("false") && !common) { + tempInstancePropertyMap.put(propKey, prop); + } + + }); + } else { + tempInstancePropertyMap.put(propKey, prop); + } + }); + + if (tempCommonPropertyMap.isEmpty() && !common) { + return tempInstancePropertyMap; + } else { + return tempCommonPropertyMap; + } + } + + /** + * Get the node types derived from those that have common properties. + * + * @param initialNodeTypes map of all the node types in the specified template + * @param filteredNodeTypes map of all the node types that have common or instance properties + * @return all node types that have common properties including their children + * @throws PfModelException on errors getting node type with common properties + */ + private Map getFinalNodeTypesMap( + Map initialNodeTypes, + Map filteredNodeTypes) { + for (var i = 0; i < initialNodeTypes.size(); i++) { + initialNodeTypes.forEach((key, nodeType) -> { + var tempToscaNodeType = new ToscaNodeType(); + tempToscaNodeType.setName(key); + + if (filteredNodeTypes.get(nodeType.getDerivedFrom()) != null) { + tempToscaNodeType.setName(key); + + var finalProps = new HashMap( + filteredNodeTypes.get(nodeType.getDerivedFrom()).getProperties()); + + tempToscaNodeType.setProperties(finalProps); + } else { + return; + } + filteredNodeTypes.putIfAbsent(key, tempToscaNodeType); + + }); + } + return filteredNodeTypes; + } + + /** + * Get the requested node types with common or instance properties. + * + * @param common boolean indicating common or instance properties + * @param name the name of the definition to get, null for all definitions + * @param version the version of the definition to get, null for all definitions + * @return the node types with common or instance properties + * @throws PfModelException on errors getting node type properties + */ + private Map getCommonOrInstancePropertiesFromNodeTypes( + boolean common, String name, String version) + throws PfModelException { + var serviceTemplates = new ToscaServiceTemplates(); + serviceTemplates.setServiceTemplates(modelsProvider.getServiceTemplateList(name, version)); + var tempNodeTypesMap = + this.getInitialNodeTypesMap(serviceTemplates.getServiceTemplates().get(0).getNodeTypes(), common); + + return this.getFinalNodeTypesMap( + serviceTemplates.getServiceTemplates().get(0).getNodeTypes(), tempNodeTypesMap); + + } + + /** + * Get node templates with appropriate common or instance properties added. + * + * @param initialNodeTemplates map of all the node templates in the specified template + * @param nodeTypeProps map of all the node types that have common or instance properties including children + * @return all node templates with appropriate common or instance properties added + * @throws PfModelException on errors getting map of node templates with common or instance properties added + */ + private Map getDerivedCommonOrInstanceNodeTemplates( + Map initialNodeTemplates, + Map nodeTypeProps) { + + var finalNodeTemplatesMap = new HashMap(); + + initialNodeTemplates.forEach((templateKey, template) -> { + if (nodeTypeProps.containsKey(template.getType())) { + var finalMergedProps = new HashMap(); + + nodeTypeProps.get(template.getType()).getProperties().forEach(finalMergedProps::putIfAbsent); + + template.setProperties(finalMergedProps); + + finalNodeTemplatesMap.put(templateKey, template); + } else { + return; + } + }); + return finalNodeTemplatesMap; + } + + /** + * Get node templates with common properties added. + * + * @param common boolean indicating common or instance properties to be used + * @param name the name of the definition to use, null for all definitions + * @param version the version of the definition to use, null for all definitions + * @return the nodes templates with common or instance properties + * @throws PfModelException on errors getting common or instance properties from node_templates + */ + public Map getNodeTemplatesWithCommonOrInstanceProperties( + boolean common, String name, String version) throws PfModelException { + + var commonOrInstanceNodeTypeProps = + this.getCommonOrInstancePropertiesFromNodeTypes(common, name, version); + + var serviceTemplates = new ToscaServiceTemplates(); + serviceTemplates.setServiceTemplates(modelsProvider.getServiceTemplateList(name, version)); + + return this.getDerivedCommonOrInstanceNodeTemplates( + serviceTemplates.getServiceTemplates().get(0).getToscaTopologyTemplate().getNodeTemplates(), + commonOrInstanceNodeTypeProps); + } + /** * Get the requested control loop definitions. * @@ -194,6 +360,30 @@ public class CommissioningProvider { return serviceTemplates.getServiceTemplates().get(0); } + /** + * Get the tosca service template with only required sections. + * + * @param name the name of the template to get, null for all definitions + * @param version the version of the template to get, null for all definitions + * @return the tosca service template + * @throws PfModelException on errors getting tosca service template + */ + public Map getToscaServiceTemplateReduced(String name, String version) throws PfModelException { + var serviceTemplates = new ToscaServiceTemplates(); + serviceTemplates.setServiceTemplates(modelsProvider.getServiceTemplateList(name, version)); + + ToscaServiceTemplate fullTemplate = serviceTemplates.getServiceTemplates().get(0); + + var template = new HashMap(); + template.put("tosca_definitions_version", fullTemplate.getToscaDefinitionsVersion()); + template.put("data_types", fullTemplate.getDataTypes()); + template.put("policy_types", fullTemplate.getPolicyTypes()); + template.put("node_types", fullTemplate.getNodeTypes()); + template.put("topology_template", fullTemplate.getToscaTopologyTemplate()); + + return template; + } + /** * Get the requested json schema. * @@ -203,9 +393,9 @@ public class CommissioningProvider { * @throws JsonProcessingException on errors generating the schema */ public String getToscaServiceTemplateSchema(String section) throws PfModelException, JsonProcessingException { - ObjectMapper mapper = new ObjectMapper(); + var mapper = new ObjectMapper(); mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); - SchemaFactoryWrapper visitor = new SchemaFactoryWrapper(); + var visitor = new SchemaFactoryWrapper(); switch (section) { case "data_types": @@ -234,9 +424,7 @@ public class CommissioningProvider { mapper.acceptJsonFormatVisitor(mapper.constructType(ToscaServiceTemplate.class), visitor); } - JsonSchema jsonSchema = visitor.finalSchema(); - String response = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonSchema); - - return response; + var jsonSchema = visitor.finalSchema(); + return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonSchema); } } diff --git a/runtime-controlloop/src/main/java/org/onap/policy/clamp/controlloop/runtime/main/rest/CommissioningController.java b/runtime-controlloop/src/main/java/org/onap/policy/clamp/controlloop/runtime/main/rest/CommissioningController.java index 74548e724..cc4ce16f7 100644 --- a/runtime-controlloop/src/main/java/org/onap/policy/clamp/controlloop/runtime/main/rest/CommissioningController.java +++ b/runtime-controlloop/src/main/java/org/onap/policy/clamp/controlloop/runtime/main/rest/CommissioningController.java @@ -345,10 +345,10 @@ public class CommissioningController extends AbstractRestController { required = false) String version) { try { - ObjectMapper mapper = new ObjectMapper(); + var mapper = new ObjectMapper(); mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); - String response = mapper.writerWithDefaultPrettyPrinter() - .writeValueAsString(provider.getToscaServiceTemplate(name, version)); + var response = mapper.writerWithDefaultPrettyPrinter() + .writeValueAsString(provider.getToscaServiceTemplateReduced(name, version)); return ResponseEntity.ok().body(response); @@ -433,6 +433,79 @@ public class CommissioningController extends AbstractRestController { } } + /** + * Retrieves the Common or Instance Properties for the specified Tosca Service Template. + * + * @param requestId request ID used in ONAP logging + * @param common a flag, true to get common properties, false to get instance properties + * @param name the name of the tosca service template to retrieve + * @param version the version of the tosca service template to get + * @return the specified tosca service template or section Json Schema + */ + // @formatter:off + @GetMapping(value = "/commission/getCommonOrInstanceProperties", + produces = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}) + @ApiOperation(value = "Query details of the requested tosca service template common or instance properties", + notes = "Queries details of the requested commissioned tosca service template json common" + + "or instance properties, returning all tosca service template common or instance property details", + response = ToscaServiceTemplate.class, + tags = {"Clamp Control Loop Commissioning API"}, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_PATCH_NAME, description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_LATEST_NAME, description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader(name = REQUEST_ID_NAME, description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class)}, + extensions = { + @Extension + ( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + } + ) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + public ResponseEntity queryToscaServiceCommonOrInstanceProperties( + @RequestHeader( + name = REQUEST_ID_NAME, + required = false) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam(value = "Flag, true for common properties, false for instance", required = false) @RequestParam( + value = "common", + defaultValue = "false", + required = false) boolean common, + @ApiParam(value = "Tosca service template name", required = false) @RequestParam( + value = "name", + required = false) String name, + @ApiParam(value = "Tosca service template version", required = true) @RequestParam( + value = "version", + required = false) String version) { + try { + return ResponseEntity.ok().body(provider.getNodeTemplatesWithCommonOrInstanceProperties( + common, name, version)); + + } catch (PfModelRuntimeException | PfModelException e) { + LOGGER.warn("Get of common or instance properties failed", e); + var resp = new CommissioningResponse(); + resp.setErrorDetails(e.getErrorResponse().getErrorMessage()); + return ResponseEntity.status(e.getErrorResponse().getResponseCode().getStatusCode()).body(resp); + } + } + /** * Queries the elements of a specific control loop. * diff --git a/runtime/extra/sql/bulkload/create-db.sql b/runtime/extra/sql/bulkload/create-db.sql index ea4d97c1b..5fa34ca04 100644 --- a/runtime/extra/sql/bulkload/create-db.sql +++ b/runtime/extra/sql/bulkload/create-db.sql @@ -7,5 +7,9 @@ USE `cldsdb4`; DROP USER 'clds'; CREATE USER 'clds'; GRANT ALL on cldsdb4.* to 'clds' identified by 'sidnnd83K' with GRANT OPTION; +CREATE DATABASE `controlloop`; +USE `controlloop`; +DROP USER 'policy'; +CREATE USER 'policy'; +GRANT ALL on controlloop.* to 'policy' identified by 'P01icY' with GRANT OPTION; FLUSH PRIVILEGES; - diff --git a/runtime/src/main/resources/clds/camel/rest/clamp-api-v2.xml b/runtime/src/main/resources/clds/camel/rest/clamp-api-v2.xml index 01ee071ee..677ec64c9 100644 --- a/runtime/src/main/resources/clds/camel/rest/clamp-api-v2.xml +++ b/runtime/src/main/resources/clds/camel/rest/clamp-api-v2.xml @@ -1724,6 +1724,41 @@ + + + + + + + + application/json + + + false + + + + + java.lang.Exception + + true + + + + + 500 + + + GET Common Properties FAILED + + + + + + + + + + + + + GET + + + application/json + + + ${header.common} + + + + + + + + + + diff --git a/runtime/src/test/java/org/onap/policy/clamp/runtime/RuntimeCommissioningResponseTestItCase.java b/runtime/src/test/java/org/onap/policy/clamp/runtime/RuntimeCommissioningResponseTestItCase.java index a1eaf27d0..f4e171174 100644 --- a/runtime/src/test/java/org/onap/policy/clamp/runtime/RuntimeCommissioningResponseTestItCase.java +++ b/runtime/src/test/java/org/onap/policy/clamp/runtime/RuntimeCommissioningResponseTestItCase.java @@ -147,4 +147,51 @@ public class RuntimeCommissioningResponseTestItCase { assertThat(HttpStatus.valueOf((Integer) exchangeResponse.getIn().getHeader(Exchange.HTTP_RESPONSE_CODE)) .is2xxSuccessful()).isTrue(); } + + @Test + public void testGetCommonOrInstancePropertiesCommonTrue() { + ProducerTemplate prodTemplate = camelContext.createProducerTemplate(); + + Exchange exchangeResponse = + prodTemplate.send("direct:get-common-or-instance-properties", ExchangeBuilder.anExchange(camelContext) + .withProperty("name", "ToscaServiceTemplate") + .withProperty("version", "1.0.0") + .withProperty("common", true) + .withProperty("raiseHttpExceptionFlag", "true") + .build()); + + assertThat(HttpStatus.valueOf((Integer) exchangeResponse.getIn().getHeader(Exchange.HTTP_RESPONSE_CODE)) + .is2xxSuccessful()).isTrue(); + } + + @Test + public void testGetCommonOrInstancePropertiesCommonFalse() { + ProducerTemplate prodTemplate = camelContext.createProducerTemplate(); + + Exchange exchangeResponse = + prodTemplate.send("direct:get-common-or-instance-properties", ExchangeBuilder.anExchange(camelContext) + .withProperty("name", "ToscaServiceTemplate") + .withProperty("version", "1.0.0") + .withProperty("common", false) + .withProperty("raiseHttpExceptionFlag", "true") + .build()); + + assertThat(HttpStatus.valueOf((Integer) exchangeResponse.getIn().getHeader(Exchange.HTTP_RESPONSE_CODE)) + .is2xxSuccessful()).isTrue(); + } + + @Test + public void testGetCommonOrInstancePropertiesCommonMissing() { + ProducerTemplate prodTemplate = camelContext.createProducerTemplate(); + + Exchange exchangeResponse = + prodTemplate.send("direct:get-common-or-instance-properties", ExchangeBuilder.anExchange(camelContext) + .withProperty("name", "ToscaServiceTemplate") + .withProperty("version", "1.0.0") + .withProperty("raiseHttpExceptionFlag", "true") + .build()); + + assertThat(HttpStatus.valueOf((Integer) exchangeResponse.getIn().getHeader(Exchange.HTTP_RESPONSE_CODE)) + .is2xxSuccessful()).isTrue(); + } } diff --git a/runtime/src/test/resources/http-cache/example/node_template.json b/runtime/src/test/resources/http-cache/example/node_template.json new file mode 100644 index 000000000..fdbfe8563 --- /dev/null +++ b/runtime/src/test/resources/http-cache/example/node_template.json @@ -0,0 +1,44 @@ +{ + "org.onap.domain.pmsh.PMSHControlLoopDefinition": { + "name": "org.onap.domain.pmsh.PMSHControlLoopDefinition", + "version": "1.2.3", + "derived_from": null, + "metadata": {}, + "description": "Control loop for Performance Management Subscription Handling", + "type": "org.onap.policy.clamp.controlloop.ControlLoop", + "type_version": "1.0.0", + "properties": { + "elements": [ + { + "name": "org.onap.domain.pmsh.PMSH_DCAEMicroservice", + "version": "1.2.3" + }, + { + "name": "org.onap.domain.pmsh.PMSH_MonitoringPolicyControlLoopElement", + "version": "1.2.3" + }, + { + "name": "org.onap.domain.pmsh.PMSH_OperationalPolicyControlLoopElement", + "version": "1.2.3" + } + ], + "provider": "Ericsson" + }, + "requirements": null, + "capabilities": null, + "identifier": { + "name": "org.onap.domain.pmsh.PMSHControlLoopDefinition", + "version": "1.2.3" + }, + "type_identifier": { + "name": "org.onap.policy.clamp.controlloop.ControlLoop", + "version": "1.0.0" + }, + "key": { + "name": "org.onap.domain.pmsh.PMSHControlLoopDefinition", + "version": "1.2.3" + }, + "defined_name": "org.onap.domain.pmsh.PMSHControlLoopDefinition", + "defined_version": "1.2.3" + } +} diff --git a/runtime/src/test/resources/http-cache/third_party_proxy.py b/runtime/src/test/resources/http-cache/third_party_proxy.py index 2a28c65d3..013388197 100644 --- a/runtime/src/test/resources/http-cache/third_party_proxy.py +++ b/runtime/src/test/resources/http-cache/third_party_proxy.py @@ -294,16 +294,24 @@ class Proxy(SimpleHTTPServer.SimpleHTTPRequestHandler): self._create_cache(jsonGenerated, cached_file_folder, cached_file_header, cached_file_content) return True elif (self.path.startswith("/onap/controlloop/v2/commission/elements")) and http_type == "GET": - print "self.path start with /commission/elements Control Loop Elements, generating response json..." - #jsondata = json.loads(self.data_string) - jsonGenerated = "[{\"name\": ,\"org.onap.domain.pmsh.PMSH_DCAEMicroservice\": [{ \"version\": \"1.2.3\", \"derived_from\": null }]}]" - self._create_cache(jsonGenerated, cached_file_folder, cached_file_header, cached_file_content) + if not _file_available: + print "self.path start with /commission/elements Control Loop Elements, generating response json..." + jsonGenerated = "[{\"name\": ,\"org.onap.domain.pmsh.PMSH_DCAEMicroservice\": [{ \"version\": \"1.2.3\", \"derived_from\": null }]}]" + self._create_cache(jsonGenerated, cached_file_folder, cached_file_header, cached_file_content) return True elif (self.path.startswith("/onap/controlloop/v2/commission")) and http_type == "GET": - print "self.path start with /commission control loop definition, generating response json..." - #jsondata = json.loads(self.data_string) - jsonGenerated = "[{\"name\": ,\"org.onap.domain.pmsh.PMSHControlLoopDefinition\": [{ \"version\": \"1.2.3\", \"derived_from\": null }]}]" - self._create_cache(jsonGenerated, cached_file_folder, cached_file_header, cached_file_content) + if not _file_available: + print "self.path start with /commission control loop definition, generating response json..." + #jsondata = json.loads(self.data_string) + jsonGenerated = "[{\"name\": ,\"org.onap.domain.pmsh.PMSHControlLoopDefinition\": [{ \"version\": \"1.2.3\", \"derived_from\": null }]}]" + self._create_cache(jsonGenerated, cached_file_folder, cached_file_header, cached_file_content) + return True + elif (self.path.startswith("/onap/controlloop/v2/commission/getCommonOrInstanceProperties")) and http_type == "GET": + if not _file_available: + print "self.path start with /commission getting common properties, generating response json..." + with open("example/node_template.json", "r") as f: + jsonGenerated = f.read() + self._create_cache(jsonGenerated, cached_file_folder, cached_file_header, cached_file_content) return True elif (self.path.startswith("/onap/controlloop/v2/commission")) and http_type == "POST": print "self.path start with POST /onap/controlloop/v2/commission, copying body to response ..." @@ -318,7 +326,6 @@ class Proxy(SimpleHTTPServer.SimpleHTTPRequestHandler): print "self.path start with /commission Decommissioning, generating response json..." jsonGenerated = "{\"errorDetails\": null,\"affectedControlLoopDefinitions\": [{ \"name\": \"ToscaServiceTemplateSimple\", \"version\": \"1.0.0\" }]}" self._create_cache(jsonGenerated, cached_file_folder, cached_file_header, cached_file_content) - return True else: return False -- cgit 1.2.3-korg