diff options
25 files changed, 416 insertions, 41 deletions
diff --git a/docs/architecture.rst b/docs/architecture.rst index fc9e6c32..c98680a2 100644 --- a/docs/architecture.rst +++ b/docs/architecture.rst @@ -12,10 +12,12 @@ update the loop with new parameters during runtime, as well as suspending and restarting it. It interacts with other systems to deploy and execute the control loop. For -example, it gets the control loop blueprint from SDC - DCAE-DS. +example, it extracts the control loop blueprint and Policy Model(Model Driven Control Loop) +from CSAR distributed by SDC/DCAE-DS. It requests from DCAE the instantiation of microservices to manage the control loop flow. Furthermore, it creates and updates multiple -policies in the Policy Engine that define the closed loop flow. +policies (for DCAE mS configuration and actual Control Operations) in the Policy Engine +that define the closed loop flow. |clamp-flow| diff --git a/docs/images/architecture/distdepl.png b/docs/images/architecture/distdepl.png Binary files differindex 5593f498..0016a859 100644 --- a/docs/images/architecture/distdepl.png +++ b/docs/images/architecture/distdepl.png diff --git a/docs/index.rst b/docs/index.rst index fc0118ac..4915722e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -36,7 +36,7 @@ CLAMP uses the API's exposed by the following ONAP components: - SDC : REST based interface exposed by the SDC, Distribution of service to DCAE - DCAE: REST based interface exposed by DCAE, Common Controller Framework, DCAE microservices onboarded (TCA, Stringmatch, Holmes (optional)) -- Policy: REST based interface (the Policy team provide a "jar" to handle the communication), both XACML and Drools PDP, APIs to App-C/VF-C/SDN-C +- Policy: REST based interface, Policy engine target both XACML and Drools PDP, Policy Engine trigger operations to App-C/VF-C/SDN-C Delivery diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 8962375c..e5305e0e 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -1,10 +1,52 @@ .. This work is licensed under a Creative Commons Attribution 4.0 International License. .. http://creativecommons.org/licenses/by/4.0 -.. Copyright (c) 2017-2018 AT&T Intellectual Property. All rights reserved. +.. Copyright (c) 2017-2019 AT&T Intellectual Property. All rights reserved. Release Notes ============= +Version: 4.0.3 +-------------- + +:Release Date: 2019-05-06 + +**New Features** + +The Dublin release is the fourth release of the Control Loop Automation Management Platform (CLAMP). + +The main goal of the Dublin release was to: + + - Stabilize Platform maturity by stabilizing CLAMP maturity matrix see `Wiki <https://wiki.onap.org/display/DW/Dublin+Release+Platform+Maturity>`_. + - CLAMP supports of Policy-model based Configuration Policy + - CLAMP supports new Policy Engine direct Rest API (no longer based on jar provided by Policy Engine) + - CLAMP main Core/UI have been reworked, removal of security issues reported by Nexus IQ. + +**Bug Fixes** + + - The full list of implemented user stories and epics is available on `DUBLIN RELEASE <https://jira.onap.org/projects/CLAMP/versions/10427>`_ + This includes the list of bugs that were fixed during the course of this release. + +**Known Issues** + + - `CLAMP-384 <https://jira.onap.org/browse/CLAMP-384>`_ Loop State in UI is not reflecting the current state + +**Security Notes** + +CLAMP code has been formally scanned during build time using NexusIQ and all Critical vulnerabilities have been addressed, items that remain open have been assessed for risk and actions to be taken in future release. +The CLAMP open Critical security vulnerabilities and their risk assessment have been documented as part of the `project <https://wiki.onap.org/pages/viewpage.action?pageId=64003444>`_. + +Quick Links: + - `CLAMP project page <https://wiki.onap.org/display/DW/CLAMP+Project>`_ + + - `Passing Badge information for CLAMP <https://bestpractices.coreinfrastructure.org/en/projects/1197>`_ + + - `Project Vulnerability Review Table for CLAMP <https://wiki.onap.org/pages/viewpage.action?pageId=64003444>`_ + +**Upgrade Notes** + + New Docker Containers are available. + + Version: 3.0.4 - maintenance release ------------------------------------ diff --git a/extra/docker/elk/docker-compose.yml b/extra/docker/elk/docker-compose.yml index 29545bb8..cb39b660 100644 --- a/extra/docker/elk/docker-compose.yml +++ b/extra/docker/elk/docker-compose.yml @@ -48,6 +48,8 @@ services: - elasticsearch volumes: - ../../../src/main/docker/kibana/saved-objects/:/saved-objects/ + - ../../../src/main/docker/kibana/conf/kibana.yml:/usr/share/kibana/config/kibana.yml + - ../../../src/main/docker/kibana/conf/keystore:/usr/share/kibana/config/keystore networks: es_net: @@ -24,7 +24,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.onap.clamp</groupId> <artifactId>clds</artifactId> - <version>4.0.2-SNAPSHOT</version> + <version>4.0.3-SNAPSHOT</version> <name>clamp</name> <!-- --> @@ -922,6 +922,19 @@ </executions> </plugin> <plugin> + <groupId>org.sonatype.plugins</groupId> + <artifactId>nexus-staging-maven-plugin</artifactId> + <version>1.6.7</version> + <extensions>true</extensions> + <configuration> + <nexusUrl>https://nexus.onap.org</nexusUrl> + <stagingProfileId>176c31dfe190a</stagingProfileId> + <serverId>ecomp-staging</serverId> + <skipNexusStagingDeployMojo>${skip.staging.artifacts}</skipNexusStagingDeployMojo> + </configuration> + </plugin> + + <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.1</version> diff --git a/src/main/docker/kibana/conf/keystore/org.onap.clamp.crt.pem b/src/main/docker/kibana/conf/keystore/org.onap.clamp.crt.pem new file mode 100644 index 00000000..ce630d43 --- /dev/null +++ b/src/main/docker/kibana/conf/keystore/org.onap.clamp.crt.pem @@ -0,0 +1,30 @@ +Bag Attributes + friendlyName: clamp@clamp.onap.org + localKeyID: 54 69 6D 65 20 31 35 35 33 37 38 37 35 31 38 33 30 33 +subject=/CN=clamp/emailAddress=/OU=clamp@clamp.onap.org/OU=OSAAF/O=ONAP/C=US +issuer=/C=US/O=ONAP/OU=OSAAF/CN=intermediateCA_9 +-----BEGIN CERTIFICATE----- +MIIEKDCCAxCgAwIBAgIIWY+5kgf/UG4wDQYJKoZIhvcNAQELBQAwRzELMAkGA1UE +BhMCVVMxDTALBgNVBAoMBE9OQVAxDjAMBgNVBAsMBU9TQUFGMRkwFwYDVQQDDBBp +bnRlcm1lZGlhdGVDQV85MB4XDTE5MDMyMTE2MTY1OFoXDTIwMDMyMTE2MTY1OFow +bDEOMAwGA1UEAwwFY2xhbXAxDzANBgkqhkiG9w0BCQEWADEdMBsGA1UECwwUY2xh +bXBAY2xhbXAub25hcC5vcmcxDjAMBgNVBAsMBU9TQUFGMQ0wCwYDVQQKDARPTkFQ +MQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALic +uDccBjOAlOsL1Z1nKnDPRTNxBwIVfARRQDxK3C0zDHQ5qEmIQlF0Vjp+bJ2rgzMW +BnodC38zt1jSXymEsekZNV2sUyBbzJl6vxvA1xJKI9VHLyPSzyUEd1H4qh8b7IDX +3GDqUJgNfvzJ94DaNnnYWFVZq/IYdLjCFaXDxPUQZtlmpdkIWBzvMeNRe4bWajau +immkmSi5/2BYQfZXHXpiKiyBnN+1FbU3consmjNwS1L+PjD+k3JLsc5ANZYZMOTp +Szhu3xmDiB3UV4gPQWacQQZEo/5exywY3Ax3TowGwIA660eSkW1L5RPdyvzEgp7A +vu4+rbhfeR5bXjy2iAUCAwEAAaOB8jCB7zAJBgNVHRMEAjAAMA4GA1UdDwEB/wQE +AwIF4DAgBgNVHSUBAf8EFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwVAYDVR0jBE0w +S4AUgfeZWxC5yIze81Je6k5poEM+rN2hMKQuMCwxDjAMBgNVBAsMBU9TQUFGMQ0w +CwYDVQQKDARPTkFQMQswCQYDVQQGEwJVU4IBBzAdBgNVHQ4EFgQU+GZ6wmWDPrmq +Wd1/NtMYiCQ8Dg4wOwYDVR0RBDQwMoIFY2xhbXCCHWNsYW1wLmFwaS5zaW1wbGVk +ZW1vLm9uYXAub3JnggpjbGFtcC5vbmFwMA0GCSqGSIb3DQEBCwUAA4IBAQCFZdhB +U6xm6l0vj4q89onLx4opTPvwGNRc0n402lifkPYXseFtphZSHIf2Sg0mFTH4KHb4 +FdMyBzq1+f5WLU+xRC1nT4eGJ0FvRR6204/fGVrzJTS67phnRnxr2WZzLPW0wPJe +K8SzN6tkUgE7/a/s0T/htE/blDxWh75+tA2jQlgj1Ri0y9A1J8wx++REKjGlHjFN +53aiipsB+wC/oEMzYL4qEPiYPI0Lr3Lsay1F7f6cvDT4+EYzBLMFuwCvpcnHgSMS +4fFj2ROmUG2+CC23B88Q0WNxjLPq/CrmHZZBsqwruPJ0cSuCQxfshTQ6uZhcjtu8 +6TRYkIcL0x9r/AHP +-----END CERTIFICATE----- diff --git a/src/main/docker/kibana/conf/keystore/org.onap.clamp.key.pem b/src/main/docker/kibana/conf/keystore/org.onap.clamp.key.pem new file mode 100644 index 00000000..fcf68bfa --- /dev/null +++ b/src/main/docker/kibana/conf/keystore/org.onap.clamp.key.pem @@ -0,0 +1,32 @@ +Bag Attributes + friendlyName: clamp@clamp.onap.org + localKeyID: 54 69 6D 65 20 31 35 35 33 37 38 37 35 31 38 33 30 33 +Key Attributes: <No Attributes> +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC4nLg3HAYzgJTr +C9WdZypwz0UzcQcCFXwEUUA8StwtMwx0OahJiEJRdFY6fmydq4MzFgZ6HQt/M7dY +0l8phLHpGTVdrFMgW8yZer8bwNcSSiPVRy8j0s8lBHdR+KofG+yA19xg6lCYDX78 +yfeA2jZ52FhVWavyGHS4whWlw8T1EGbZZqXZCFgc7zHjUXuG1mo2ropppJkouf9g +WEH2Vx16YiosgZzftRW1N3KJ7JozcEtS/j4w/pNyS7HOQDWWGTDk6Us4bt8Zg4gd +1FeID0FmnEEGRKP+XscsGNwMd06MBsCAOutHkpFtS+UT3cr8xIKewL7uPq24X3ke +W148togFAgMBAAECggEATncV+R5pKFS7dteV2IvzxvTh1cZxkxoslu0t3zJ2OKPc +5D1pYK+QeGx5Be2cHru6TOlMoXRc4ZjKke8AUXY74/Y573GB91vtL0KznYkuIHDw +oALcb153eqVWTbniHMzSjcSxv2N4E9iQo8L39oVI6CrjCIvPgFuSqMCdUNJPkVTI +4nsarTfLK4fzi7IbWzi9JdE1QRNIxcCMcYJRnLZMdneMLBleR0UL82Xc2KOy5SEt +zyKYCQ8zS247FKolnOrDkhKxXI5fzdDpRK5AQSsAykUPWlYq7pzKjY/dU9rMRohx +YSltFjPZ3sQ3UKqqIqhZS+GoVuZoc925WyhViPsqtQKBgQDsL4LFfPWN8nnsusQp +VR3T7HvvwXuEVAydlaJMwZU0cRYN+L7RHHjDoXZZrNJDIDzNoWnBLKRGx3mtLmgJ +9Pa6SxN6Oc8oo6jzv2D59g1PVjNOMOYTCTb/2Xum4LMLaeeF57HkWxzeA3Ws47++ +gXwzQpbE90tp1Ys4uXD3JoivvwKBgQDIGZTwLGhLSegdAjG83WEgmdtzT1kjvx0Q +A8IR2jkgkTJHdKiuslJ8Z3/XufHEwWMWwfs1XLwxYluoo1y9eNvNeHZXjLqjL62c +I3034F9IvvTUqFcxam2WdoklXbAiSvLUo/9exPgOuVxok6Zv1imRgGb/vYV9vyG7 +86MRuQu5OwKBgQC9E3fcA6JMpY3H3uhEsngzfMDm+fyYvfRvfyezzNFWbyWZv8V6 +gBGJg0vMlFarGDa044BW/hbw9qXI5zqwpeOS1aFdGsRlo0cRAuduk/Spy7c85FZ7 +bMgT4BZmTMHo5DpNb2NxDSO59AkThCuvJde47ZjnS5WavzI6EfKGWNnZ3wKBgQCF +QiwjCp/mS/DtqLFxAsmVSYGROG231aXILYiIFRloa+ndFn7j4NP4D4FfLHErRFL2 +K/ddIUYfaU57b1fqwts26ht90LXWyYDH9AaHOMCcFLe+C+INgcA7rPNG1C7hl6JC +JHmEJo7AV4eICZSU9D44rRdrB08oYCpaHjYiLmb1UwKBgQCWCDJ4p2DrNL9hzj3K +kzvM5saXrfI4aVBXVt9rw9s1d/WG8JOpnmHcnLPb6Tj59rDktrLCLv0sVstMwNVJ +sOO+qsgn1VoZalcVhhjdONm5YvhJQgz0F7Y2xkr6g/AuMPz2YigGfm7fe/z7rc+L +q9Ua2HmUS8DDBy7W89MNZJNkDQ== +-----END PRIVATE KEY----- diff --git a/src/main/docker/kibana/conf/kibana.yml b/src/main/docker/kibana/conf/kibana.yml new file mode 100644 index 00000000..0c4eda9a --- /dev/null +++ b/src/main/docker/kibana/conf/kibana.yml @@ -0,0 +1,9 @@ +--- +# Default Kibana configuration from kibana-docker. + +server.name: kibana +server.host: "0" +elasticsearch.hosts: http://elasticsearch:9200 +server.ssl.enabled: true +server.ssl.key: /usr/share/kibana/config/keystore/org.onap.clamp.key.pem +server.ssl.certificate: /usr/share/kibana/config/keystore/org.onap.clamp.crt.pem
\ No newline at end of file diff --git a/src/main/java/org/onap/clamp/clds/config/CamelConfiguration.java b/src/main/java/org/onap/clamp/clds/config/CamelConfiguration.java index de6e4dcd..3dc80738 100644 --- a/src/main/java/org/onap/clamp/clds/config/CamelConfiguration.java +++ b/src/main/java/org/onap/clamp/clds/config/CamelConfiguration.java @@ -118,8 +118,7 @@ public class CamelConfiguration extends RouteBuilder { .apiContextPath("api-doc").apiVendorExtension(true).apiProperty("api.title", "Clamp Rest API") .apiProperty("api.version", ClampVersioning.getCldsVersionFromProps()) .apiProperty("base.path", "/restservices/clds/"); - // .apiProperty("cors", "true"); - camelContext.setTracing(true); + // camelContext.setTracing(true); configureDefaultSslProperties(); registerTrustStore(); diff --git a/src/main/java/org/onap/clamp/loop/Loop.java b/src/main/java/org/onap/clamp/loop/Loop.java index 89770860..6de2863e 100644 --- a/src/main/java/org/onap/clamp/loop/Loop.java +++ b/src/main/java/org/onap/clamp/loop/Loop.java @@ -23,6 +23,8 @@ package org.onap.clamp.loop; +import com.att.eelf.configuration.EELFLogger; +import com.att.eelf.configuration.EELFManager; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonObject; @@ -47,6 +49,7 @@ import javax.persistence.ManyToMany; import javax.persistence.OneToMany; import javax.persistence.OrderBy; import javax.persistence.Table; +import javax.persistence.Transient; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; @@ -66,6 +69,9 @@ public class Loop implements Serializable { */ private static final long serialVersionUID = -286522707701388642L; + @Transient + private static final EELFLogger logger = EELFManager.getInstance().getLogger(Loop.class); + @Id @Expose @Column(nullable = false, name = "name", unique = true) @@ -279,7 +285,9 @@ public class Loop implements Serializable { jsonArray.add(policyNode); policyNode.addProperty("policy-id", policyName); } - return new GsonBuilder().setPrettyPrinting().create().toJson(jsonObject); + String payload = new GsonBuilder().setPrettyPrinting().create().toJson(jsonObject); + logger.info("PdpGroup policy payload: " + payload); + return payload; } /** diff --git a/src/main/java/org/onap/clamp/policy/microservice/MicroServicePolicy.java b/src/main/java/org/onap/clamp/policy/microservice/MicroServicePolicy.java index 2bbb9118..d8d15a5b 100644 --- a/src/main/java/org/onap/clamp/policy/microservice/MicroServicePolicy.java +++ b/src/main/java/org/onap/clamp/policy/microservice/MicroServicePolicy.java @@ -23,6 +23,8 @@ package org.onap.clamp.policy.microservice; +import com.att.eelf.configuration.EELFLogger; +import com.att.eelf.configuration.EELFManager; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; @@ -40,6 +42,7 @@ import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.ManyToMany; import javax.persistence.Table; +import javax.persistence.Transient; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; @@ -61,6 +64,9 @@ public class MicroServicePolicy implements Serializable, Policy { */ private static final long serialVersionUID = 6271238288583332616L; + @Transient + private static final EELFLogger logger = EELFManager.getInstance().getLogger(MicroServicePolicy.class); + @Expose @Id @Column(nullable = false, name = "name", unique = true) @@ -271,7 +277,9 @@ public class MicroServicePolicy implements Serializable, Policy { JsonObject policyProperties = new JsonObject(); policyDetails.add("properties", policyProperties); policyProperties.add(this.getMicroServicePropertyNameFromTosca(toscaJson), this.getProperties()); - return new GsonBuilder().setPrettyPrinting().create().toJson(policyPayloadResult); + String policyPayload = new GsonBuilder().setPrettyPrinting().create().toJson(policyPayloadResult); + logger.info("Micro service policy payload: " + policyPayload); + return policyPayload; } } diff --git a/src/main/java/org/onap/clamp/policy/operational/LegacyOperationalPolicy.java b/src/main/java/org/onap/clamp/policy/operational/LegacyOperationalPolicy.java new file mode 100644 index 00000000..33148f0b --- /dev/null +++ b/src/main/java/org/onap/clamp/policy/operational/LegacyOperationalPolicy.java @@ -0,0 +1,150 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP CLAMP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END============================================ + * =================================================================== + * + */ + +package org.onap.clamp.policy.operational; + +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; + +import org.apache.commons.lang3.math.NumberUtils; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.DumperOptions.ScalarStyle; +import org.yaml.snakeyaml.Yaml; + +/** + * + * This class contains the code required to support the sending of Legacy + * operational payload to policy engine. This will probably disappear in El + * Alto. + * + */ +public class LegacyOperationalPolicy { + + private LegacyOperationalPolicy() { + + } + + private static void translateStringValues(String jsonKey, String stringValue, JsonElement parentJsonElement) { + if (stringValue.equalsIgnoreCase("true") || stringValue.equalsIgnoreCase("false")) { + parentJsonElement.getAsJsonObject().addProperty(jsonKey, Boolean.valueOf(stringValue)); + + } else if (NumberUtils.isParsable(stringValue)) { + parentJsonElement.getAsJsonObject().addProperty(jsonKey, Long.parseLong(stringValue)); + } + } + + private static JsonElement removeAllQuotes(JsonElement jsonElement) { + if (jsonElement.isJsonArray()) { + for (JsonElement element : jsonElement.getAsJsonArray()) { + removeAllQuotes(element); + } + } else if (jsonElement.isJsonObject()) { + for (Entry<String, JsonElement> entry : jsonElement.getAsJsonObject().entrySet()) { + if (entry.getValue().isJsonPrimitive() && entry.getValue().getAsJsonPrimitive().isString()) { + translateStringValues(entry.getKey(), entry.getValue().getAsString(), jsonElement); + } else { + removeAllQuotes(entry.getValue()); + } + } + } + return jsonElement; + } + + public static JsonElement reworkPayloadAttributes(JsonElement policyJson) { + for (JsonElement policy : policyJson.getAsJsonObject().get("policies").getAsJsonArray()) { + JsonElement payloadElem = policy.getAsJsonObject().get("payload"); + String payloadString = payloadElem != null ? payloadElem.getAsString() : ""; + if (!payloadString.isEmpty()) { + Map<String, String> testMap = new Yaml().load(payloadString); + String json = new GsonBuilder().create().toJson(testMap); + policy.getAsJsonObject().add("payload", new GsonBuilder().create().fromJson(json, JsonElement.class)); + } + } + return policyJson; + } + + private static void replacePropertiesIfEmpty(JsonElement policy, String key, String valueIfEmpty) { + JsonElement payloadElem = policy.getAsJsonObject().get(key); + String payloadString = payloadElem != null ? payloadElem.getAsString() : ""; + if (payloadString.isEmpty()) { + policy.getAsJsonObject().addProperty(key, valueIfEmpty); + } + } + + private static JsonElement fulfillPoliciesTreeField(JsonElement policyJson) { + for (JsonElement policy : policyJson.getAsJsonObject().get("policies").getAsJsonArray()) { + replacePropertiesIfEmpty(policy, "success", "final_success"); + replacePropertiesIfEmpty(policy, "failure", "final_failure"); + replacePropertiesIfEmpty(policy, "failure_timeout", "final_failure_timeout"); + replacePropertiesIfEmpty(policy, "failure_retries", "final_failure_retries"); + replacePropertiesIfEmpty(policy, "failure_exception", "final_failure_exception"); + replacePropertiesIfEmpty(policy, "failure_guard", "final_failure_guard"); + } + return policyJson; + } + + private static Map<String, Object> createMap(JsonElement jsonElement) { + Map<String, Object> mapResult = new TreeMap<>(); + + if (jsonElement.isJsonObject()) { + for (Entry<String, JsonElement> entry : jsonElement.getAsJsonObject().entrySet()) { + if (entry.getValue().isJsonPrimitive() && entry.getValue().getAsJsonPrimitive().isString()) { + mapResult.put(entry.getKey(), entry.getValue().getAsString()); + } else if (entry.getValue().isJsonPrimitive() && entry.getValue().getAsJsonPrimitive().isBoolean()) { + mapResult.put(entry.getKey(), entry.getValue().getAsBoolean()); + } else if (entry.getValue().isJsonPrimitive() && entry.getValue().getAsJsonPrimitive().isNumber()) { + // Only int ro long normally, we don't need float here + mapResult.put(entry.getKey(), entry.getValue().getAsLong()); + } else if (entry.getValue().isJsonArray()) { + List<Map<String, Object>> newArray = new ArrayList<>(); + mapResult.put(entry.getKey(), newArray); + for (JsonElement element : entry.getValue().getAsJsonArray()) { + newArray.add(createMap(element)); + } + } else if (entry.getValue().isJsonObject()) { + mapResult.put(entry.getKey(), createMap(entry.getValue())); + } + } + } + return mapResult; + } + + public static String createPolicyPayloadYamlLegacy(JsonElement operationalPolicyJsonElement) { + JsonElement opPolicy = fulfillPoliciesTreeField( + removeAllQuotes(reworkPayloadAttributes(operationalPolicyJsonElement.getAsJsonObject().deepCopy()))); + Map<?, ?> jsonMap = createMap(opPolicy); + DumperOptions options = new DumperOptions(); + options.setDefaultScalarStyle(ScalarStyle.PLAIN); + options.setIndent(2); + options.setPrettyFlow(true); + // Policy can't support { } in the yaml + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + return (new Yaml(options)).dump(jsonMap); + } +} diff --git a/src/main/java/org/onap/clamp/policy/operational/OperationalPolicy.java b/src/main/java/org/onap/clamp/policy/operational/OperationalPolicy.java index 906c3cfa..62c5a1e9 100644 --- a/src/main/java/org/onap/clamp/policy/operational/OperationalPolicy.java +++ b/src/main/java/org/onap/clamp/policy/operational/OperationalPolicy.java @@ -23,6 +23,8 @@ package org.onap.clamp.policy.operational; +import com.att.eelf.configuration.EELFLogger; +import com.att.eelf.configuration.EELFManager; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; @@ -45,6 +47,7 @@ import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; +import javax.persistence.Transient; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; @@ -52,6 +55,7 @@ import org.hibernate.annotations.TypeDefs; import org.onap.clamp.dao.model.jsontype.StringJsonUserType; import org.onap.clamp.loop.Loop; import org.onap.clamp.policy.Policy; +import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; @Entity @@ -63,6 +67,9 @@ public class OperationalPolicy implements Serializable, Policy { */ private static final long serialVersionUID = 6117076450841538255L; + @Transient + private static final EELFLogger logger = EELFManager.getInstance().getLogger(OperationalPolicy.class); + @Id @Expose @Column(nullable = false, name = "name", unique = true) @@ -176,21 +183,33 @@ public class OperationalPolicy implements Serializable, Policy { operationalPolicyDetails.add("metadata", metadata); metadata.addProperty("policy-id", this.name); - operationalPolicyDetails.add("properties", this.configurationsJson.get("operational_policy")); + operationalPolicyDetails.add("properties", LegacyOperationalPolicy + .reworkPayloadAttributes(this.configurationsJson.get("operational_policy").deepCopy())); Gson gson = new GsonBuilder().create(); + Map<?, ?> jsonMap = gson.fromJson(gson.toJson(policyPayloadResult), Map.class); - return (new Yaml()).dump(jsonMap); + + DumperOptions options = new DumperOptions(); + options.setIndent(2); + options.setPrettyFlow(true); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + + return (new Yaml(options)).dump(jsonMap); } @Override public String createPolicyPayload() throws UnsupportedEncodingException { - // Now the Yaml payload must be injected in a json ... + // Now using the legacy payload fo Dublin JsonObject payload = new JsonObject(); payload.addProperty("policy-id", this.getName()); - payload.addProperty("content", URLEncoder.encode(createPolicyPayloadYaml(), StandardCharsets.UTF_8.toString())); - return new GsonBuilder().setPrettyPrinting().create().toJson(payload); + payload.addProperty("content", URLEncoder.encode( + LegacyOperationalPolicy.createPolicyPayloadYamlLegacy(this.configurationsJson.get("operational_policy")), + StandardCharsets.UTF_8.toString())); + String opPayload = new GsonBuilder().setPrettyPrinting().create().toJson(payload); + logger.info("Operational policy payload: " + opPayload); + return opPayload; } /** @@ -210,6 +229,7 @@ public class OperationalPolicy implements Serializable, Policy { result.put(guardElem.getKey(), new GsonBuilder().create().toJson(guard)); } } + logger.info("Guard policy payload: " + result); return result; } diff --git a/src/main/resources/META-INF/resources/designer/partials/portfolios/operational_policy_window.html b/src/main/resources/META-INF/resources/designer/partials/portfolios/operational_policy_window.html index 3e958668..db127f7d 100644 --- a/src/main/resources/META-INF/resources/designer/partials/portfolios/operational_policy_window.html +++ b/src/main/resources/META-INF/resources/designer/partials/portfolios/operational_policy_window.html @@ -303,17 +303,17 @@ label { <select class="form-control" name="type" id="type" ng-click="initTargetResourceId($event)" ng-model="type"> <option value="">-- choose an option --</option> - <option value="VFModule">VFModule</option> + <option value="VFMODULE">VFMODULE</option> <option value="VM">VM</option> <option value="VNF">VNF</option> </select> </div> </div> <div class="form-group clearfix"> - <label for="resourceId" class="col-sm-4 control-label"> + <label for="resourceID" class="col-sm-4 control-label"> Target ResourceId</label> <div class="col-sm-8"> - <select class="form-control" name="resourceId" id="resourceId" + <select class="form-control" name="resourceID" id="resourceID" enableFilter="true" ng-click="changeTargetResourceId($event)" ng-model="resourceId"> <option value="">-- choose an option --</option> diff --git a/src/main/resources/META-INF/resources/designer/scripts/OperationalPolicyCtrl.js b/src/main/resources/META-INF/resources/designer/scripts/OperationalPolicyCtrl.js index 95fc4203..0f9b62ff 100644 --- a/src/main/resources/META-INF/resources/designer/scripts/OperationalPolicyCtrl.js +++ b/src/main/resources/META-INF/resources/designer/scripts/OperationalPolicyCtrl.js @@ -234,27 +234,27 @@ app } function initTargetResourceIdOptions (targetType, formNum) { var recipe = $("#formId" + formNum + "#recipe").val(); - $("#formId" + formNum + " #resourceId").empty(); - $("#formId" + formNum + " #resourceId").append($('<option></option>').val("").html("-- choose an option --")); + $("#formId" + formNum + " #resourceID").empty(); + $("#formId" + formNum + " #resourceID").append($('<option></option>').val("").html("-- choose an option --")); var resourceVnf = getResourceDetailsVfProperty(); if (targetType == "VNF" && (null !== resourceVnf || undefined !== resourceVnf)) { for ( var prop in resourceVnf) { var name = resourceVnf[prop]["name"]; - $("#formId" + formNum + " #resourceId").append($('<option></option>').val(name).html(name)); + $("#formId" + formNum + " #resourceID").append($('<option></option>').val(name).html(name)); } } var resourceVFModule = getResourceDetailsVfModuleProperty(); - if (targetType == "VFModule" && (null !== resourceVFModule || undefined !== resourceVFModule)) { + if (targetType == "VFMODULE" && (null !== resourceVFModule || undefined !== resourceVFModule)) { if (recipe == 'VF Module Create' || recipe == 'VF Module Delete') { for ( var prop in resourceVFModule) { if (resourceVFModule[prop]["isBase"] == false) { - $("#formId" + formNum + " #resourceId").append($('<option></option>').val(resourceVFModule[prop]["vfModuleModelName"]).html(resourceVFModule[prop]["vfModuleModelName"])); + $("#formId" + formNum + " #resourceID").append($('<option></option>').val(resourceVFModule[prop]["vfModuleModelName"]).html(resourceVFModule[prop]["vfModuleModelName"])); } } } else { for ( var prop in resourceVFModule) { - $("#formId" + formNum + " #resourceId").append($('<option></option>').val(resourceVFModule[prop]["vfModuleModelName"]).html(resourceVFModule[prop]["vfModuleModelName"])); + $("#formId" + formNum + " #resourceID").append($('<option></option>').val(resourceVFModule[prop]["vfModuleModelName"]).html(resourceVFModule[prop]["vfModuleModelName"])); } } } @@ -306,7 +306,7 @@ app var resourceVFModule = getResourceDetailsVfModuleProperty(); var type = $("#formId" + formNum +" #type").val(); var recipe = $("#formId" + formNum +" #recipe").val(); - if (type == "VFModule" && (null !== resourceVFModule || undefined !== resourceVFModule) + if (type == "VFMODULE" && (null !== resourceVFModule || undefined !== resourceVFModule) && (recipe == 'VF Module Create' || recipe == 'VF Module Delete')) { for ( var prop in resourceVFModule) { if (prop == $(event.target).val()) { diff --git a/src/main/resources/META-INF/resources/designer/scripts/authcontroller.js b/src/main/resources/META-INF/resources/designer/scripts/authcontroller.js index d6387c86..92bd24fd 100644 --- a/src/main/resources/META-INF/resources/designer/scripts/authcontroller.js +++ b/src/main/resources/META-INF/resources/designer/scripts/authcontroller.js @@ -34,7 +34,6 @@ angular.module('clds-app').controller( function($scope, $rootScope, $window, $resource, $http, $location, $cookies) { console.log("//////////AuthenticateCtrl"); $scope.getInclude = function() { - console.log("getInclude011111111"); var invalidUser = $window.localStorage.getItem("invalidUser"); var isAuth = $window.localStorage.getItem("isAuth"); if (invalidUser == 'true') diff --git a/src/main/resources/clds/camel/routes/flexible-flow.xml b/src/main/resources/clds/camel/routes/flexible-flow.xml index 89ff24be..1293d95c 100644 --- a/src/main/resources/clds/camel/routes/flexible-flow.xml +++ b/src/main/resources/clds/camel/routes/flexible-flow.xml @@ -271,9 +271,9 @@ </setHeader> <log loggingLevel="INFO" - message="Endpoint to delete operational policy: {{clamp.config.policy.api.url}}/policy/api/v1/policytypes/onap.policies.controlloop.Operational/versions/1.0.0/policies/${exchangeProperty[operationalPolicy].getName()}/versions/1.0.0"></log> + message="Endpoint to delete operational policy: {{clamp.config.policy.api.url}}/policy/api/v1/policytypes/onap.policies.controlloop.Operational/versions/1.0.0/policies/${exchangeProperty[operationalPolicy].getName()}/versions/1"></log> <toD - uri="{{clamp.config.policy.api.url}}/policy/api/v1/policytypes/onap.policies.controlloop.Operational/versions/1.0.0/policies/${exchangeProperty[operationalPolicy].getName()}/versions/1.0.0?bridgeEndpoint=true&useSystemProperties=true&mapHttpMessageHeaders=false&throwExceptionOnFailure=${exchangeProperty[raiseHttpExceptionFlag]}&httpClient.connectTimeout=10000&deleteWithBody=false&mapHttpMessageBody=false&mapHttpMessageFormUrlEncodedBody=false&authUsername={{clamp.config.policy.api.userName}}&authPassword={{clamp.config.policy.api.password}}" /> + uri="{{clamp.config.policy.api.url}}/policy/api/v1/policytypes/onap.policies.controlloop.Operational/versions/1.0.0/policies/${exchangeProperty[operationalPolicy].getName()}/versions/1?bridgeEndpoint=true&useSystemProperties=true&mapHttpMessageHeaders=false&throwExceptionOnFailure=${exchangeProperty[raiseHttpExceptionFlag]}&httpClient.connectTimeout=10000&deleteWithBody=false&mapHttpMessageBody=false&mapHttpMessageFormUrlEncodedBody=false&authUsername={{clamp.config.policy.api.userName}}&authPassword={{clamp.config.policy.api.password}}" /> <doFinally> <to uri="direct:reset-raise-http-exception-flag" /> <to @@ -364,9 +364,9 @@ </setHeader> <log loggingLevel="INFO" - message="Endpoint to delete guard policy: {{clamp.config.policy.api.url}}/policy/api/v1/policytypes/onap.policies.controlloop.Guard/versions/1.0.0/policies/${exchangeProperty[guardPolicy].getKey()}/versions/1.0.0"></log> + message="Endpoint to delete guard policy: {{clamp.config.policy.api.url}}/policy/api/v1/policytypes/onap.policies.controlloop.Guard/versions/1.0.0/policies/${exchangeProperty[guardPolicy].getKey()}/versions/1"></log> <toD - uri="{{clamp.config.policy.api.url}}/policy/api/v1/policytypes/onap.policies.controlloop.Guard/versions/1.0.0/policies/${exchangeProperty[guardPolicy].getKey()}/versions/1.0.0?bridgeEndpoint=true&useSystemProperties=true&throwExceptionOnFailure=${exchangeProperty[raiseHttpExceptionFlag]}&httpClient.connectTimeout=10000&deleteWithBody=false&mapHttpMessageBody=false&mapHttpMessageFormUrlEncodedBody=false&authUsername={{clamp.config.policy.api.userName}}&authPassword={{clamp.config.policy.api.password}}" /> + uri="{{clamp.config.policy.api.url}}/policy/api/v1/policytypes/onap.policies.controlloop.Guard/versions/1.0.0/policies/${exchangeProperty[guardPolicy].getKey()}/versions/1?bridgeEndpoint=true&useSystemProperties=true&throwExceptionOnFailure=${exchangeProperty[raiseHttpExceptionFlag]}&httpClient.connectTimeout=10000&deleteWithBody=false&mapHttpMessageBody=false&mapHttpMessageFormUrlEncodedBody=false&authUsername={{clamp.config.policy.api.userName}}&authPassword={{clamp.config.policy.api.password}}" /> <doFinally> <to uri="direct:reset-raise-http-exception-flag" /> diff --git a/src/test/java/org/onap/clamp/policy/microservice/OperationalPolicyPayloadTest.java b/src/test/java/org/onap/clamp/policy/microservice/OperationalPolicyPayloadTest.java index b12ca89f..8972e511 100644 --- a/src/test/java/org/onap/clamp/policy/microservice/OperationalPolicyPayloadTest.java +++ b/src/test/java/org/onap/clamp/policy/microservice/OperationalPolicyPayloadTest.java @@ -33,6 +33,7 @@ import java.util.Map; import org.junit.Test; import org.onap.clamp.clds.util.ResourceFileUtil; +import org.onap.clamp.policy.operational.LegacyOperationalPolicy; import org.onap.clamp.policy.operational.OperationalPolicy; import org.skyscreamer.jsonassert.JSONAssert; @@ -43,13 +44,23 @@ public class OperationalPolicyPayloadTest { JsonObject jsonConfig = new GsonBuilder().create().fromJson( ResourceFileUtil.getResourceAsString("tosca/operational-policy-properties.json"), JsonObject.class); OperationalPolicy policy = new OperationalPolicy("testPolicy", null, jsonConfig); + assertThat(policy.createPolicyPayloadYaml()) .isEqualTo(ResourceFileUtil.getResourceAsString("tosca/operational-policy-payload.yaml")); + assertThat(policy.createPolicyPayload()) .isEqualTo(ResourceFileUtil.getResourceAsString("tosca/operational-policy-payload.json")); } @Test + public void testLegacyOperationalPolicyPayloadConstruction() throws IOException { + JsonObject jsonConfig = new GsonBuilder().create().fromJson( + ResourceFileUtil.getResourceAsString("tosca/operational-policy-properties.json"), JsonObject.class); + assertThat(LegacyOperationalPolicy.createPolicyPayloadYamlLegacy(jsonConfig.get("operational_policy"))) + .isEqualTo(ResourceFileUtil.getResourceAsString("tosca/operational-policy-payload-legacy.yaml")); + } + + @Test public void testGuardPolicyEmptyPayloadConstruction() throws IOException { JsonObject jsonConfig = new GsonBuilder().create().fromJson( ResourceFileUtil.getResourceAsString("tosca/operational-policy-no-guard-properties.json"), diff --git a/src/test/resources/tosca/operational-policy-no-guard-properties.json b/src/test/resources/tosca/operational-policy-no-guard-properties.json index 30c04404..fdb1906a 100644 --- a/src/test/resources/tosca/operational-policy-no-guard-properties.json +++ b/src/test/resources/tosca/operational-policy-no-guard-properties.json @@ -22,7 +22,7 @@ "failure_guard": "", "target": { "type": "VM", - "resourceId": "", + "resourceID": "", "modelInvariantId": "", "modelVersionId": "", "modelName": "", diff --git a/src/test/resources/tosca/operational-policy-payload-legacy.yaml b/src/test/resources/tosca/operational-policy-payload-legacy.yaml new file mode 100644 index 00000000..41184c9c --- /dev/null +++ b/src/test/resources/tosca/operational-policy-payload-legacy.yaml @@ -0,0 +1,39 @@ +controlLoop: + abatement: true + controlLoopName: control loop + timeout: 30 + trigger_policy: new1 + version: 2.0.0 +policies: +- actor: SO + failure: new2 + failure_exception: new2 + failure_guard: new2 + failure_retries: new2 + failure_timeout: new2 + id: new1 + payload: + configurationParameters: '[{"ip-addr":"$.vf-module-topology.vf-module-parameters.param[10].value","oam-ip-addr":"$.vf-module-topology.vf-module-parameters.param[15].value","enabled":"$.vf-module-topology.vf-module-parameters.param[22].value"}]' + requestParameters: '{"usePreload":true,"userParams":[]}' + recipe: Rebuild + retry: 10 + success: new2 + target: + resourceTargetId: test + type: VFC + timeout: 20 +- actor: SDNC + failure: final_failure + failure_exception: final_failure_exception + failure_guard: final_failure_guard + failure_retries: final_failure_retries + failure_timeout: final_failure_timeout + id: new2 + payload: '' + recipe: Migrate + retry: 30 + success: final_success + target: + resourceTargetId: test + type: VFC + timeout: 40 diff --git a/src/test/resources/tosca/operational-policy-payload.json b/src/test/resources/tosca/operational-policy-payload.json index 1017d0a2..5097654d 100644 --- a/src/test/resources/tosca/operational-policy-payload.json +++ b/src/test/resources/tosca/operational-policy-payload.json @@ -1,4 +1,4 @@ { "policy-id": "testPolicy", - "content": "tosca_definitions_version%3A+tosca_simple_yaml_1_0_0%0Atopology_template%3A%0A++policies%3A%0A++-+testPolicy%3A%0A++++++type%3A+onap.policies.controlloop.Operational%0A++++++version%3A+1.0.0%0A++++++metadata%3A+%7Bpolicy-id%3A+testPolicy%7D%0A++++++properties%3A%0A++++++++controlLoop%3A+%7BcontrolLoopName%3A+control+loop%2C+version%3A+2.0.0%2C+trigger_policy%3A+new1%2C%0A++++++++++timeout%3A+%2730%27%2C+abatement%3A+%27true%27%7D%0A++++++++policies%3A%0A++++++++-+id%3A+new1%0A++++++++++recipe%3A+Rebuild%0A++++++++++retry%3A+%2710%27%0A++++++++++timeout%3A+%2720%27%0A++++++++++actor%3A+SO%0A++++++++++payload%3A+test%0A++++++++++success%3A+new2%0A++++++++++failure%3A+new2%0A++++++++++failure_timeout%3A+new2%0A++++++++++failure_retries%3A+new2%0A++++++++++failure_exception%3A+new2%0A++++++++++failure_guard%3A+new2%0A++++++++++target%3A+%7Btype%3A+VFC%2C+resourceTargetId%3A+test%7D%0A++++++++-+id%3A+new2%0A++++++++++recipe%3A+Migrate%0A++++++++++retry%3A+%2730%27%0A++++++++++timeout%3A+%2740%27%0A++++++++++actor%3A+SDNC%0A++++++++++payload%3A+test%0A++++++++++target%3A+%7Btype%3A+VFC%2C+resourceTargetId%3A+test%7D%0A" + "content": "controlLoop%3A%0A++abatement%3A+true%0A++controlLoopName%3A+control+loop%0A++timeout%3A+30%0A++trigger_policy%3A+new1%0A++version%3A+2.0.0%0Apolicies%3A%0A-+actor%3A+SO%0A++failure%3A+new2%0A++failure_exception%3A+new2%0A++failure_guard%3A+new2%0A++failure_retries%3A+new2%0A++failure_timeout%3A+new2%0A++id%3A+new1%0A++payload%3A%0A++++configurationParameters%3A+%27%5B%7B%22ip-addr%22%3A%22%24.vf-module-topology.vf-module-parameters.param%5B10%5D.value%22%2C%22oam-ip-addr%22%3A%22%24.vf-module-topology.vf-module-parameters.param%5B15%5D.value%22%2C%22enabled%22%3A%22%24.vf-module-topology.vf-module-parameters.param%5B22%5D.value%22%7D%5D%27%0A++++requestParameters%3A+%27%7B%22usePreload%22%3Atrue%2C%22userParams%22%3A%5B%5D%7D%27%0A++recipe%3A+Rebuild%0A++retry%3A+10%0A++success%3A+new2%0A++target%3A%0A++++resourceTargetId%3A+test%0A++++type%3A+VFC%0A++timeout%3A+20%0A-+actor%3A+SDNC%0A++failure%3A+final_failure%0A++failure_exception%3A+final_failure_exception%0A++failure_guard%3A+final_failure_guard%0A++failure_retries%3A+final_failure_retries%0A++failure_timeout%3A+final_failure_timeout%0A++id%3A+new2%0A++payload%3A+%27%27%0A++recipe%3A+Migrate%0A++retry%3A+30%0A++success%3A+final_success%0A++target%3A%0A++++resourceTargetId%3A+test%0A++++type%3A+VFC%0A++timeout%3A+40%0A" }
\ No newline at end of file diff --git a/src/test/resources/tosca/operational-policy-payload.yaml b/src/test/resources/tosca/operational-policy-payload.yaml index 68116b00..c3a6b5c2 100644 --- a/src/test/resources/tosca/operational-policy-payload.yaml +++ b/src/test/resources/tosca/operational-policy-payload.yaml @@ -4,28 +4,39 @@ topology_template: - testPolicy: type: onap.policies.controlloop.Operational version: 1.0.0 - metadata: {policy-id: testPolicy} + metadata: + policy-id: testPolicy properties: - controlLoop: {controlLoopName: control loop, version: 2.0.0, trigger_policy: new1, - timeout: '30', abatement: 'true'} + controlLoop: + controlLoopName: control loop + version: 2.0.0 + trigger_policy: new1 + timeout: '30' + abatement: 'true' policies: - id: new1 recipe: Rebuild retry: '10' timeout: '20' actor: SO - payload: test + payload: + requestParameters: '{"usePreload":true,"userParams":[]}' + configurationParameters: '[{"ip-addr":"$.vf-module-topology.vf-module-parameters.param[10].value","oam-ip-addr":"$.vf-module-topology.vf-module-parameters.param[15].value","enabled":"$.vf-module-topology.vf-module-parameters.param[22].value"}]' success: new2 failure: new2 failure_timeout: new2 failure_retries: new2 failure_exception: new2 failure_guard: new2 - target: {type: VFC, resourceTargetId: test} + target: + type: VFC + resourceTargetId: test - id: new2 recipe: Migrate retry: '30' timeout: '40' actor: SDNC - payload: test - target: {type: VFC, resourceTargetId: test} + payload: '' + target: + type: VFC + resourceTargetId: test diff --git a/src/test/resources/tosca/operational-policy-properties.json b/src/test/resources/tosca/operational-policy-properties.json index 52eabb8a..bfce6b33 100644 --- a/src/test/resources/tosca/operational-policy-properties.json +++ b/src/test/resources/tosca/operational-policy-properties.json @@ -42,7 +42,7 @@ "retry": "10", "timeout": "20", "actor": "SO", - "payload": "test", + "payload": "requestParameters: '{\"usePreload\":true,\"userParams\":[]}'\r\nconfigurationParameters: '[{\"ip-addr\":\"$.vf-module-topology.vf-module-parameters.param[10].value\",\"oam-ip-addr\":\"$.vf-module-topology.vf-module-parameters.param[15].value\",\"enabled\":\"$.vf-module-topology.vf-module-parameters.param[22].value\"}]'", "success": "new2", "failure": "new2", "failure_timeout": "new2", @@ -60,7 +60,7 @@ "retry": "30", "timeout": "40", "actor": "SDNC", - "payload": "test", + "payload": "", "target": { "type": "VFC", "resourceTargetId": "test" diff --git a/version.properties b/version.properties index 9ef20f15..d222de82 100644 --- a/version.properties +++ b/version.properties @@ -27,7 +27,7 @@ major=4 minor=0 -patch=2 +patch=3 base_version=${major}.${minor}.${patch} |