From 6efc32aa77960316bcb1ea93bf042e5b1b2dd303 Mon Sep 17 00:00:00 2001 From: Marcus G K Williams Date: Mon, 8 Apr 2019 21:59:29 -0700 Subject: Refactor OofHomingV2 Refactor OofHomingV2 Java to working state and fix IT tests. Issue-ID: SO-1469 Change-Id: I8647058adf5ed41c5bebebee79f9dbc4d80c2974 Signed-off-by: Marcus G K Williams --- .../onap/so/bpmn/buildingblock/OofHomingV2.java | 112 ++-- .../onap/so/client/oof/beans/LicenseDemand.java | 97 ++++ .../org/onap/so/client/oof/beans/LicenseInfo.java | 49 ++ .../org/onap/so/client/oof/beans/OofRequest.java | 6 +- .../onap/so/client/oof/beans/PlacementDemand.java | 12 + .../onap/so/client/oof/beans/PlacementInfo.java | 2 +- .../test/java/org/onap/so/BaseIntegrationTest.java | 8 + .../flowspecific/tasks/OofHomingV2IT.java | 594 +++++++++++++++++++++ .../org/onap/so/client/oof/OofClientTestIT.java | 9 +- .../BuildingBlocks/OofHoming/oofRequest1Vpn.json | 58 ++ .../OofHoming/oofRequest1VpnOpt.json | 61 +++ .../BuildingBlocks/OofHoming/oofRequest3Ar.json | 67 +++ .../BuildingBlocks/OofHoming/oofRequest3Vpn.json | 91 ++++ 13 files changed, 1097 insertions(+), 69 deletions(-) create mode 100644 bpmn/so-bpmn-tasks/src/main/java/org/onap/so/client/oof/beans/LicenseDemand.java create mode 100644 bpmn/so-bpmn-tasks/src/main/java/org/onap/so/client/oof/beans/LicenseInfo.java create mode 100644 bpmn/so-bpmn-tasks/src/test/java/org/onap/so/bpmn/infrastructure/flowspecific/tasks/OofHomingV2IT.java create mode 100644 bpmn/so-bpmn-tasks/src/test/resources/__files/BuildingBlocks/OofHoming/oofRequest1Vpn.json create mode 100644 bpmn/so-bpmn-tasks/src/test/resources/__files/BuildingBlocks/OofHoming/oofRequest1VpnOpt.json create mode 100644 bpmn/so-bpmn-tasks/src/test/resources/__files/BuildingBlocks/OofHoming/oofRequest3Ar.json create mode 100644 bpmn/so-bpmn-tasks/src/test/resources/__files/BuildingBlocks/OofHoming/oofRequest3Vpn.json (limited to 'bpmn/so-bpmn-tasks/src') diff --git a/bpmn/so-bpmn-tasks/src/main/java/org/onap/so/bpmn/buildingblock/OofHomingV2.java b/bpmn/so-bpmn-tasks/src/main/java/org/onap/so/bpmn/buildingblock/OofHomingV2.java index 2524fc7892..2696313daf 100644 --- a/bpmn/so-bpmn-tasks/src/main/java/org/onap/so/bpmn/buildingblock/OofHomingV2.java +++ b/bpmn/so-bpmn-tasks/src/main/java/org/onap/so/bpmn/buildingblock/OofHomingV2.java @@ -2,7 +2,7 @@ * ============LICENSE_START======================================================= * ONAP - SO * ================================================================================ - * Copyright (C) 2017 - 2018 AT&T Intellectual Property. All rights reserved. + * Copyright (C) 2019 Intel Corp. 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. @@ -21,6 +21,7 @@ package org.onap.so.bpmn.buildingblock; import org.apache.commons.lang.SerializationUtils; +import org.apache.commons.lang.exception.ExceptionUtils; import org.camunda.bpm.engine.delegate.BpmnError; import java.util.ArrayList; import org.json.JSONArray; @@ -38,7 +39,6 @@ import org.onap.so.bpmn.servicedecomposition.bbobjects.VpnBondingLink; import org.onap.so.bpmn.servicedecomposition.entities.GeneralBuildingBlock; import org.onap.so.bpmn.servicedecomposition.generalobjects.License; import org.onap.so.bpmn.servicedecomposition.generalobjects.RequestContext; -import org.onap.so.bpmn.servicedecomposition.generalobjects.RequestParameters; import org.onap.so.bpmn.servicedecomposition.homingobjects.Candidate; import org.onap.so.bpmn.servicedecomposition.homingobjects.CandidateType; import org.onap.so.bpmn.servicedecomposition.homingobjects.SolutionCandidates; @@ -49,13 +49,14 @@ import org.onap.so.client.exception.BadResponseException; import org.onap.so.client.exception.ExceptionBuilder; import org.onap.so.client.oof.OofClient; import org.onap.so.client.oof.OofValidator; +import org.onap.so.client.oof.beans.LicenseDemand; +import org.onap.so.client.oof.beans.LicenseInfo; import org.onap.so.client.oof.beans.ModelInfo; import org.onap.so.client.oof.beans.OofRequest; import org.onap.so.client.oof.beans.OofRequestParameters; import org.onap.so.client.oof.beans.PlacementDemand; import org.onap.so.client.oof.beans.PlacementInfo; import org.onap.so.client.oof.beans.RequestInfo; -import org.onap.so.client.oof.beans.ResourceModelInfo; import org.onap.so.client.oof.beans.ServiceInfo; import org.onap.so.client.oof.beans.SubscriberInfo; import org.onap.so.db.catalog.beans.OrchestrationStatus; @@ -85,7 +86,7 @@ public class OofHomingV2 { @Autowired private Environment env; @Autowired - private OofClient client; + private OofClient oofClient; @Autowired private OofValidator oofValidator; @Autowired @@ -98,7 +99,6 @@ public class OofHomingV2 { private static final String RESOURCE_MODULE_NAME = "resourceModuleName"; private static final String RESOURCE_MODEL_INFO = "resourceModelInfo"; private static final String IDENTIFIER_TYPE = "identifierType"; - private static final String INVENTORY_TYPE = "inventoryType"; private static final String SOLUTIONS = "solutions"; private static final String RESOURCE_MISSING_DATA = "Resource does not contain: "; private static final String SERVICE_MISSING_DATA = "Service Instance does not contain: "; @@ -111,12 +111,11 @@ public class OofHomingV2 { * @param execution */ public void callOof(BuildingBlockExecution execution) { - logger.trace("Started Sniro Homing Call Sniro"); + logger.trace("Started Oof Homing Call Oof"); try { GeneralBuildingBlock bb = execution.getGeneralBuildingBlock(); RequestContext requestContext = bb.getRequestContext(); - RequestParameters requestParams = requestContext.getRequestParameters(); String requestId = requestContext.getMsoRequestId(); ServiceInstance serviceInstance = bb.getCustomer().getServiceSubscription().getServiceInstances().get(0); @@ -127,28 +126,24 @@ public class OofHomingV2 { timeout = env.getProperty("oof.timeout", "PT30M"); } - OofRequest request = new OofRequest(); + OofRequest oofRequest = new OofRequest(); RequestInfo requestInfo = (RequestInfo) buildRequestInfo(requestId, timeout); - request.setRequestInformation(requestInfo); + oofRequest.setRequestInformation(requestInfo); ServiceInfo serviceInfo = buildServiceInfo(serviceInstance); - request.setServiceInformation(serviceInfo); + oofRequest.setServiceInformation(serviceInfo); - PlacementInfo placementInfo = buildPlacementInfo(customer, requestParams); + PlacementInfo placementInfo = buildPlacementInfo(customer); - ArrayList placementDemands = buildPlacementDemands(serviceInstance); - placementInfo.setPlacementDemands(placementDemands); - request.setPlacementInformation(placementInfo); + placementInfo = buildPlacementDemands(serviceInstance, placementInfo); + oofRequest.setPlacementInformation(placementInfo); - JSONObject licenseInfo = new JSONObject(); + LicenseInfo licenseInfo = buildLicenseInfo(serviceInstance); + oofRequest.setLicenseInformation(licenseInfo); - JSONArray licenseDemands = buildLicenseDemands(serviceInstance); - licenseInfo.put("licenseDemands", licenseDemands); - request.setLicenseInformation(licenseInfo.toString()); - - if (placementDemands.size() > 0 || licenseDemands.length() > 0) { - client.postDemands(request); + if (!placementInfo.getPlacementDemands().isEmpty() || !licenseInfo.getLicenseDemands().isEmpty()) { + oofClient.postDemands(oofRequest); } else { logger.debug(SERVICE_MISSING_DATA + " resources eligible for homing or licensing"); throw new BpmnError(UNPROCESSABLE, @@ -162,12 +157,15 @@ public class OofHomingV2 { logger.trace("Completed Oof Homing Call Oof"); } catch (BpmnError e) { + logger.debug(" Error - while preparing oof request: " + e.getStackTrace()); exceptionUtil.buildAndThrowWorkflowException(execution, Integer.parseInt(e.getErrorCode()), e.getMessage()); } catch (BadResponseException e) { + logger.debug(" Error - while preparing oof request: " + e.getStackTrace()); exceptionUtil.buildAndThrowWorkflowException(execution, 400, e.getMessage()); } catch (Exception e) { - exceptionUtil.buildAndThrowWorkflowException(execution, INTERNAL, - "Internal Error - occurred while preparing oof request: " + e.getMessage()); + logger.debug(" Error - while preparing oof request: " + e.getStackTrace()); + exceptionUtil.buildAndThrowWorkflowException(execution, INTERNAL, "Internal Error - occurred while " + + "preparing oof request: " + e + " Stack:" + ExceptionUtils.getFullStackTrace(e)); } } @@ -240,6 +238,10 @@ public class OofHomingV2 { requestInfo.setSourceId("mso"); requestInfo.setRequestType("create"); requestInfo.setTimeout(timeoutSeconds); + requestInfo.setNumSolutions(1); + ArrayList optimizers = new ArrayList(); + optimizers.add("placement"); + requestInfo.setOptimizers(optimizers); } else { throw new BpmnError(UNPROCESSABLE, "Request Context does not contain: requestId"); } @@ -270,7 +272,7 @@ public class OofHomingV2 { * Builds initial section of placement info for the homing/licensing request * */ - private PlacementInfo buildPlacementInfo(Customer customer, RequestParameters requestParams) { + private PlacementInfo buildPlacementInfo(Customer customer) { PlacementInfo placementInfo = new PlacementInfo(); if (customer != null) { logger.debug("Adding subscriber to placement information"); @@ -279,22 +281,11 @@ public class OofHomingV2 { subscriberInfo.setSubscriberName(customer.getSubscriberName()); subscriberInfo.setSubscriberCommonSiteId(customer.getSubscriberCommonSiteId()); placementInfo.setSubscriberInfo(subscriberInfo); - if (requestParams != null) { - logger.debug("Adding request parameters to placement information"); - OofRequestParameters oofRequestParams = new OofRequestParameters(); - for (Map requestParam : requestParams.getUserParams()) { - if (requestParam.containsKey("customerLatitude")) { - oofRequestParams.setCustomerLatitude(requestParam.get("customerLatitude").toString()); - } - if (requestParam.containsKey("customerLongitude")) { - oofRequestParams.setCustomerLongitude(requestParam.get("customerLongitude").toString()); - } - if (requestParam.containsKey("customerName")) { - oofRequestParams.setCustomerName(requestParam.get("customerName").toString()); - } - } - placementInfo.setRequestParameters(oofRequestParams); - } + OofRequestParameters oofRequestParams = new OofRequestParameters(); + oofRequestParams.setCustomerLatitude(customer.getCustomerLatitude()); + oofRequestParams.setCustomerLongitude(customer.getCustomerLongitude()); + oofRequestParams.setCustomerName(customer.getSubscriberName()); + placementInfo.setRequestParameters(oofRequestParams); } else { throw new BpmnError(UNPROCESSABLE, SERVICE_MISSING_DATA + "customer"); } @@ -306,9 +297,8 @@ public class OofHomingV2 { * Builds the placement demand list for the homing/licensing request * */ - private ArrayList buildPlacementDemands(ServiceInstance serviceInstance) { + private PlacementInfo buildPlacementDemands(ServiceInstance serviceInstance, PlacementInfo placementInfo) { logger.trace("Building placement information demands"); - ArrayList placementDemands = new ArrayList(); List allottedResourceList = serviceInstance.getAllottedResources(); if (!allottedResourceList.isEmpty()) { @@ -319,7 +309,7 @@ public class OofHomingV2 { } PlacementDemand demand = buildDemand(ar.getId(), ar.getModelInfoAllottedResource()); // addCandidates(ar, demand); - placementDemands.add(demand); + placementInfo.getPlacementDemands().add(demand); } } List vpnBondingLinkList = serviceInstance.getVpnBondingLinks(); @@ -333,42 +323,43 @@ public class OofHomingV2 { } PlacementDemand demand = buildDemand(sp.getId(), sp.getModelInfoServiceProxy()); // addCandidates(sp, demand); - placementDemands.add(demand); + placementInfo.getPlacementDemands().add(demand); } } } - return placementDemands; + return placementInfo; } /** * Builds the license demand list for the homing/licensing request * */ - private JSONArray buildLicenseDemands(ServiceInstance serviceInstance) { + private LicenseInfo buildLicenseInfo(ServiceInstance serviceInstance) { logger.trace("Building license information"); - JSONArray licenseDemands = new JSONArray(); + LicenseInfo licenseInfo = new LicenseInfo(); List vnfList = serviceInstance.getVnfs(); if (!vnfList.isEmpty()) { logger.debug("Adding vnfs to license demands list"); for (GenericVnf vnf : vnfList) { - JSONObject demand = buildLicenseDemand(vnf.getVnfId(), vnf.getModelInfoGenericVnf()); - licenseDemands.put(demand); + LicenseDemand demand = buildLicenseDemand(vnf.getVnfId(), vnf.getModelInfoGenericVnf()); + licenseInfo.getLicenseDemands().add(demand); } } - return licenseDemands; + return licenseInfo; } /** * Builds a single license demand object * */ - private JSONObject buildLicenseDemand(String id, ModelInfoMetadata metadata) { + private LicenseDemand buildLicenseDemand(String id, ModelInfoMetadata metadata) { logger.debug("Building demand for service or resource: " + id); - JSONObject demand = new JSONObject(); + LicenseDemand demand = new LicenseDemand(); if (isNotBlank(id) && isNotBlank(metadata.getModelInstanceName())) { - demand.put(SERVICE_RESOURCE_ID, id); - demand.put(RESOURCE_MODULE_NAME, metadata.getModelInstanceName()); - demand.put(RESOURCE_MODEL_INFO, buildModelInfo(metadata)); + + demand.setServiceResourceId(id); + demand.setResourceModuleName(metadata.getModelInstanceName()); + demand.setResourceModelInfo(buildModelInfo(metadata)); } else { throw new BpmnError(UNPROCESSABLE, RESOURCE_MISSING_DATA + "modelInstanceName"); } @@ -385,7 +376,7 @@ public class OofHomingV2 { if (isNotBlank(id) && isNotBlank(metadata.getModelInstanceName())) { placementDemand.setServiceResourceId(id); placementDemand.setResourceModuleName(metadata.getModelInstanceName()); - placementDemand.setResourceModelInfo((ResourceModelInfo) buildModelInfo(metadata)); + placementDemand.setResourceModelInfo(buildModelInfo(metadata)); } else { throw new BpmnError(UNPROCESSABLE, RESOURCE_MISSING_DATA + "modelInstanceName"); } @@ -525,11 +516,11 @@ public class OofHomingV2 { JSONArray assignments = placement.getJSONArray("assignmentInfo"); Map assignmentsMap = jsonUtils.entryArrayToMap(assignments.toString(), "key", "value"); solutionInfo.setRehome(Boolean.parseBoolean(assignmentsMap.get("isRehome"))); - String type = placement.getString(INVENTORY_TYPE); + String type = identifierType; ServiceInstance si = new ServiceInstance(); CloudRegion cloud = setCloud(assignmentsMap); - if (type.equals("service")) { + if (type.equals("serviceInstanceId")) { if (identifierType.equals(CandidateType.SERVICE_INSTANCE_ID.toString())) { solutionInfo.setHomed(true); si.setServiceInstanceId(identifierValue); @@ -556,7 +547,7 @@ public class OofHomingV2 { logger.debug(invalidMessage + IDENTIFIER_TYPE); throw new BpmnError(UNPROCESSABLE, invalidMessage + IDENTIFIER_TYPE); } - } else if (type.equals("cloud")) { + } else if (type.equals("cloudRegionId")) { if (identifierType.equals(CandidateType.CLOUD_REGION_ID.toString())) { logger.debug("Resources has been homed to a cloud region"); cloud.setLcpCloudRegionId(identifierValue); @@ -567,9 +558,6 @@ public class OofHomingV2 { logger.debug(invalidMessage + IDENTIFIER_TYPE); throw new BpmnError(UNPROCESSABLE, invalidMessage + IDENTIFIER_TYPE); } - } else { - logger.debug(invalidMessage + INVENTORY_TYPE); - throw new BpmnError(UNPROCESSABLE, invalidMessage + INVENTORY_TYPE); } si.setSolutionInfo(solutionInfo); return si; diff --git a/bpmn/so-bpmn-tasks/src/main/java/org/onap/so/client/oof/beans/LicenseDemand.java b/bpmn/so-bpmn-tasks/src/main/java/org/onap/so/client/oof/beans/LicenseDemand.java new file mode 100644 index 0000000000..e64a5450b5 --- /dev/null +++ b/bpmn/so-bpmn-tasks/src/main/java/org/onap/so/client/oof/beans/LicenseDemand.java @@ -0,0 +1,97 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP - SO + * ================================================================================ + * Copyright (C) 2019 Intel Corp. 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.so.client.oof.beans; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonRootName; +import java.io.Serializable; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({"resourceModuleName", "serviceResourceId", "tenantId", "resourceModelInfo"}) +@JsonRootName("licenseDemand") +public class LicenseDemand implements Serializable { + + private static final long serialVersionUID = -759180997599143791L; + + @JsonProperty("resourceModuleName") + private String resourceModuleName; + @JsonProperty("serviceResourceId") + private String serviceResourceId; + @JsonProperty("tenantId") + private String tenantId; + @JsonProperty("resourceModelInfo") + private ResourceModelInfo resourceModelInfo; + + @JsonProperty("resourceModuleName") + public String getResourceModuleName() { + return resourceModuleName; + } + + @JsonProperty("resourceModuleName") + public void setResourceModuleName(String resourceModuleName) { + this.resourceModuleName = resourceModuleName; + } + + @JsonProperty("serviceResourceId") + public String getServiceResourceId() { + return serviceResourceId; + } + + @JsonProperty("serviceResourceId") + public void setServiceResourceId(String serviceResourceId) { + this.serviceResourceId = serviceResourceId; + } + + @JsonProperty("tenantId") + public String getTenantId() { + return tenantId; + } + + @JsonProperty("tenantId") + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + @JsonProperty("resourceModelInfo") + public ResourceModelInfo getResourceModelInfo() { + return resourceModelInfo; + } + + @JsonProperty("resourceModelInfo") + public void setResourceModelInfo(ResourceModelInfo resourceModelInfo) { + this.resourceModelInfo = resourceModelInfo; + } + + public void setResourceModelInfo(ModelInfo modelInfo) { + ResourceModelInfo localResourceModelInfo = new ResourceModelInfo(); + localResourceModelInfo.setModelVersionId(modelInfo.getModelVersionId()); + localResourceModelInfo.setModelVersionId(modelInfo.getModelVersionId()); + localResourceModelInfo.setModelVersion(modelInfo.getModelVersion()); + localResourceModelInfo.setModelName(modelInfo.getModelName()); + localResourceModelInfo.setModelType(modelInfo.getModelType()); + localResourceModelInfo.setModelInvariantId(modelInfo.getModelInvariantId()); + localResourceModelInfo.setModelCustomizationName(modelInfo.getModelCustomizationName()); + this.resourceModelInfo = localResourceModelInfo; + } + +} diff --git a/bpmn/so-bpmn-tasks/src/main/java/org/onap/so/client/oof/beans/LicenseInfo.java b/bpmn/so-bpmn-tasks/src/main/java/org/onap/so/client/oof/beans/LicenseInfo.java new file mode 100644 index 0000000000..74ff9339d3 --- /dev/null +++ b/bpmn/so-bpmn-tasks/src/main/java/org/onap/so/client/oof/beans/LicenseInfo.java @@ -0,0 +1,49 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP - SO + * ================================================================================ + * Copyright (C) 2019 Intel Corp. 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.so.client.oof.beans; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; +import java.io.Serializable; +import java.util.ArrayList; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonRootName("licenseInfo") +public class LicenseInfo implements Serializable { + + private static final long serialVersionUID = -759180997599143791L; + + @JsonProperty("licenseDemands") + private ArrayList licenseDemands = new ArrayList<>(); + + + @JsonProperty("licenseDemands") + public ArrayList getLicenseDemands() { + return licenseDemands; + } + + @JsonProperty("licenseDemands") + public void setLicenseDemands(ArrayList licenseDemands) { + this.licenseDemands = licenseDemands; + } + +} diff --git a/bpmn/so-bpmn-tasks/src/main/java/org/onap/so/client/oof/beans/OofRequest.java b/bpmn/so-bpmn-tasks/src/main/java/org/onap/so/client/oof/beans/OofRequest.java index e3c29fe04d..f8896240ba 100644 --- a/bpmn/so-bpmn-tasks/src/main/java/org/onap/so/client/oof/beans/OofRequest.java +++ b/bpmn/so-bpmn-tasks/src/main/java/org/onap/so/client/oof/beans/OofRequest.java @@ -46,7 +46,7 @@ public class OofRequest implements Serializable { private PlacementInfo placementInformation; @JsonProperty("licenseInfo") - private String licenseInformation; + private LicenseInfo licenseInformation; public RequestInfo getRequestInformation() { @@ -73,11 +73,11 @@ public class OofRequest implements Serializable { this.placementInformation = placementInformation; } - public String getLicenseInformation() { + public LicenseInfo getLicenseInformation() { return licenseInformation; } - public void setLicenseInformation(String licenseInformation) { + public void setLicenseInformation(LicenseInfo licenseInformation) { this.licenseInformation = licenseInformation; } diff --git a/bpmn/so-bpmn-tasks/src/main/java/org/onap/so/client/oof/beans/PlacementDemand.java b/bpmn/so-bpmn-tasks/src/main/java/org/onap/so/client/oof/beans/PlacementDemand.java index 73c100df6d..631b3707d4 100644 --- a/bpmn/so-bpmn-tasks/src/main/java/org/onap/so/client/oof/beans/PlacementDemand.java +++ b/bpmn/so-bpmn-tasks/src/main/java/org/onap/so/client/oof/beans/PlacementDemand.java @@ -82,4 +82,16 @@ public class PlacementDemand implements Serializable { this.resourceModelInfo = resourceModelInfo; } + public void setResourceModelInfo(ModelInfo modelInfo) { + ResourceModelInfo localResourceModelInfo = new ResourceModelInfo(); + localResourceModelInfo.setModelVersionId(modelInfo.getModelVersionId()); + localResourceModelInfo.setModelVersionId(modelInfo.getModelVersionId()); + localResourceModelInfo.setModelVersion(modelInfo.getModelVersion()); + localResourceModelInfo.setModelName(modelInfo.getModelName()); + localResourceModelInfo.setModelType(modelInfo.getModelType()); + localResourceModelInfo.setModelInvariantId(modelInfo.getModelInvariantId()); + localResourceModelInfo.setModelCustomizationName(modelInfo.getModelCustomizationName()); + this.resourceModelInfo = localResourceModelInfo; + } + } diff --git a/bpmn/so-bpmn-tasks/src/main/java/org/onap/so/client/oof/beans/PlacementInfo.java b/bpmn/so-bpmn-tasks/src/main/java/org/onap/so/client/oof/beans/PlacementInfo.java index 0eb14d991a..7519e8c87e 100644 --- a/bpmn/so-bpmn-tasks/src/main/java/org/onap/so/client/oof/beans/PlacementInfo.java +++ b/bpmn/so-bpmn-tasks/src/main/java/org/onap/so/client/oof/beans/PlacementInfo.java @@ -39,7 +39,7 @@ public class PlacementInfo implements Serializable { @JsonProperty("subscriberInfo") private SubscriberInfo subscriberInfo; @JsonProperty("placementDemands") - private ArrayList placementDemands = null; + private ArrayList placementDemands = new ArrayList<>(); @JsonProperty("requestParameters") public OofRequestParameters getRequestParameters() { diff --git a/bpmn/so-bpmn-tasks/src/test/java/org/onap/so/BaseIntegrationTest.java b/bpmn/so-bpmn-tasks/src/test/java/org/onap/so/BaseIntegrationTest.java index 3bb8fb70f2..43e4ce3ebd 100644 --- a/bpmn/so-bpmn-tasks/src/test/java/org/onap/so/BaseIntegrationTest.java +++ b/bpmn/so-bpmn-tasks/src/test/java/org/onap/so/BaseIntegrationTest.java @@ -23,9 +23,11 @@ import java.io.IOException; import java.io.InputStream; import org.junit.Before; import org.junit.runner.RunWith; +import org.onap.so.bpmn.buildingblock.OofHomingV2; import org.onap.so.bpmn.buildingblock.SniroHomingV2; import org.onap.so.bpmn.common.data.TestDataSetup; import org.onap.so.client.appc.ApplicationControllerAction; +import org.onap.so.client.oof.OofClient; import org.onap.so.client.orchestration.SDNOHealthCheckResources; import org.onap.so.client.sdnc.SDNCClient; import org.onap.so.client.sniro.SniroClient; @@ -63,6 +65,12 @@ public abstract class BaseIntegrationTest extends TestDataSetup { @SpyBean protected SniroClient sniroClient; + @SpyBean + protected OofHomingV2 oofHoming; + + @SpyBean + protected OofClient oofClient; + @MockBean protected ApplicationControllerAction appCClient; diff --git a/bpmn/so-bpmn-tasks/src/test/java/org/onap/so/bpmn/infrastructure/flowspecific/tasks/OofHomingV2IT.java b/bpmn/so-bpmn-tasks/src/test/java/org/onap/so/bpmn/infrastructure/flowspecific/tasks/OofHomingV2IT.java new file mode 100644 index 0000000000..e066058796 --- /dev/null +++ b/bpmn/so-bpmn-tasks/src/test/java/org/onap/so/bpmn/infrastructure/flowspecific/tasks/OofHomingV2IT.java @@ -0,0 +1,594 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP - SO + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. & Intel Corp. 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.so.bpmn.infrastructure.flowspecific.tasks; + +import com.fasterxml.jackson.core.JsonProcessingException; +import org.camunda.bpm.engine.delegate.BpmnError; +import org.json.JSONArray; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.onap.so.BaseIntegrationTest; +import org.onap.so.bpmn.servicedecomposition.bbobjects.AllottedResource; +import org.onap.so.bpmn.servicedecomposition.bbobjects.Customer; +import org.onap.so.bpmn.servicedecomposition.bbobjects.GenericVnf; +import org.onap.so.bpmn.servicedecomposition.bbobjects.ServiceInstance; +import org.onap.so.bpmn.servicedecomposition.bbobjects.ServiceProxy; +import org.onap.so.bpmn.servicedecomposition.bbobjects.VpnBondingLink; +import org.onap.so.bpmn.servicedecomposition.generalobjects.RequestContext; +import org.onap.so.bpmn.servicedecomposition.generalobjects.RequestParameters; +import org.onap.so.bpmn.servicedecomposition.homingobjects.Candidate; +import org.onap.so.bpmn.servicedecomposition.homingobjects.CandidateType; +import org.onap.so.client.exception.BadResponseException; +import org.onap.so.client.oof.beans.OofRequest; +import org.skyscreamer.jsonassert.JSONAssert; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class OofHomingV2IT extends BaseIntegrationTest { + + private ServiceInstance serviceInstance; + + private RequestContext requestContext; + + private Customer customer; + + private static final String RESOURCE_PATH = "__files/BuildingBlocks/OofHoming/"; + + + String mockResponse = + "{\"transactionId\": \"123456789\", \"requestId\": \"1234\", \"statusMessage\": \"homing request accepted\", \"requestStatus\": \"accepted\"}"; + + @Before + public void before() { + serviceInstance = setServiceInstance(); + customer = setCustomer(); + customer.setGlobalCustomerId("testCustomerId"); + customer.setSubscriberName("testCustomerName"); + customer.getServiceSubscription().getServiceInstances().add(serviceInstance); + customer.setCustomerLatitude("customerLatitude"); + customer.setCustomerLongitude("customerLongitude"); + + requestContext = setRequestContext(); + requestContext.setMsoRequestId("requestId"); + + RequestParameters params = new RequestParameters(); + params.setaLaCarte(false); + params.setSubscriptionServiceType("testSubscriptionServiceType"); + requestContext.setRequestParameters(params); + } + + public void beforeVpnBondingLink(String id) { + VpnBondingLink bondingLink = new VpnBondingLink(); + bondingLink.setVpnBondingLinkId("testVpnBondingId" + id); + bondingLink.getServiceProxies().add(setServiceProxy("1", "transport")); + ServiceProxy sp2 = setServiceProxy("2", "infrastructure"); + Candidate requiredCandidate = new Candidate(); + requiredCandidate.setIdentifierType(CandidateType.VNF_ID); + List c = new ArrayList(); + c.add("testVnfId"); + requiredCandidate.setIdentifiers(c); + sp2.addRequiredCandidates(requiredCandidate); + bondingLink.getServiceProxies().add(sp2); + serviceInstance.getVpnBondingLinks().add(bondingLink); + + } + + public void beforeAllottedResource() { + serviceInstance.getAllottedResources().add(setAllottedResource("1")); + serviceInstance.getAllottedResources().add(setAllottedResource("2")); + serviceInstance.getAllottedResources().add(setAllottedResource("3")); + } + + public void beforeVnf() { + setGenericVnf(); + } + + @Test + public void testCallOof_success_1VpnLink() throws BadResponseException, IOException { + beforeVpnBondingLink("1"); + + wireMockServer.stubFor(post(urlEqualTo("/api/oof/v1/placement")).willReturn( + aResponse().withStatus(200).withHeader("Content-Type", "application/json").withBody(mockResponse))); + + oofHoming.callOof(execution); + + String request = readResourceFile(RESOURCE_PATH + "oofRequest1Vpn.json"); + request = request.replace("28080", wireMockPort); + + ArgumentCaptor argument = ArgumentCaptor.forClass(OofRequest.class); + verify(oofClient, times(1)).postDemands(argument.capture()); + JSONAssert.assertEquals(request, argument.getValue().toJsonString(), false); + } + + @Test + public void testCallOof_success_3VpnLink() throws JsonProcessingException, BadResponseException { + beforeVpnBondingLink("1"); + beforeVpnBondingLink("2"); + beforeVpnBondingLink("3"); + + wireMockServer.stubFor(post(urlEqualTo("/api/oof/v1/placement")).willReturn( + aResponse().withStatus(200).withHeader("Content-Type", "application/json").withBody(mockResponse))); + + oofHoming.callOof(execution); + + String request = readResourceFile(RESOURCE_PATH + "oofRequest3Vpn.json"); + request = request.replace("28080", wireMockPort); + + ArgumentCaptor argument = ArgumentCaptor.forClass(OofRequest.class); + verify(oofClient, times(1)).postDemands(argument.capture()); + JSONAssert.assertEquals(request, argument.getValue().toJsonString(), false); + } + + @Test + public void testCallOof_success_3Allotteds() throws BadResponseException, JsonProcessingException { + beforeAllottedResource(); + + wireMockServer.stubFor(post(urlEqualTo("/api/oof/v1/placement")).willReturn( + aResponse().withStatus(200).withHeader("Content-Type", "application/json").withBody(mockResponse))); + + oofHoming.callOof(execution); + + String request = readResourceFile(RESOURCE_PATH + "oofRequest3Ar.json"); + request = request.replace("28080", wireMockPort); + + ArgumentCaptor argument = ArgumentCaptor.forClass(OofRequest.class); + verify(oofClient, times(1)).postDemands(argument.capture()); + JSONAssert.assertEquals(request, argument.getValue().toJsonString(), false); + } + + @Test + public void testCallOof_success_1Vnf() throws JsonProcessingException, BadResponseException { + beforeVnf(); + + wireMockServer.stubFor(post(urlEqualTo("/api/oof/v1/placement")).willReturn( + aResponse().withStatus(200).withHeader("Content-Type", "application/json").withBody(mockResponse))); + + oofHoming.callOof(execution); + + ArgumentCaptor argument = ArgumentCaptor.forClass(OofRequest.class); + verify(oofClient, times(1)).postDemands(argument.capture()); + // TODO assertEquals(request, argument.getValue().toJsonString()); + } + + @Test + public void testCallOof_success_3Allotteds1Vnf() throws JsonProcessingException, BadResponseException { + beforeAllottedResource(); + beforeVnf(); + + wireMockServer.stubFor(post(urlEqualTo("/api/oof/v1/placement")).willReturn( + aResponse().withStatus(200).withHeader("Content-Type", "application/json").withBody(mockResponse))); + + oofHoming.callOof(execution); + + verify(oofClient, times(1)).postDemands(isA(OofRequest.class)); + } + + @Test + public void testProcessSolution_success_1VpnLink_1Solution() { + beforeVpnBondingLink("1"); + + JSONObject asyncResponse = new JSONObject(); + asyncResponse.put("transactionId", "transactionId").put("requestId", "testRequestId").put("requestState", + "completed"); + JSONArray solution1 = new JSONArray(); + solution1.put(new JSONObject().put("serviceResourceId", "testProxyId1") + .put("solution", + new JSONObject().put("identifierType", "serviceInstanceId").put("identifiers", + new JSONArray().put("testServiceInstanceId1"))) + .put("assignmentInfo", + new JSONArray().put(new JSONObject().put("key", "isRehome").put("value", "False")) + .put(new JSONObject().put("key", "cloudOwner").put("value", "aic")) + .put(new JSONObject().put("key", "vnfHostName").put("value", "testVnfHostName1")) + .put(new JSONObject().put("key", "aicClli").put("value", "testAicClli1")) + .put(new JSONObject().put("key", "aicVersion").put("value", "3")) + .put(new JSONObject().put("key", "vnfId").put("value", "testVnfId1")) + .put(new JSONObject().put("key", "cloudRegionId").put("value", "testSloudRegionId1")))); + solution1.put(new JSONObject().put("serviceResourceId", "testProxyId2") + .put("solution", + new JSONObject().put("identifierType", "serviceInstanceId").put("identifiers", + new JSONArray().put("testServiceInstanceId2"))) + .put("assignmentInfo", + new JSONArray().put(new JSONObject().put("key", "isRehome").put("value", "False")) + .put(new JSONObject().put("key", "cloudOwner").put("value", "aic")) + .put(new JSONObject().put("key", "primaryPnfName").put("value", "testPrimaryPnfName2")) + .put(new JSONObject().put("key", "aicClli").put("value", "testAicClli2")) + .put(new JSONObject().put("key", "aicVersion").put("value", "3")) + .put(new JSONObject().put("key", "secondaryPnfName").put("value", + "testSecondaryPnfName2")) + .put(new JSONObject().put("key", "cloudRegionId").put("value", "testSloudRegionId2")))); + + asyncResponse.put("solutions", new JSONObject().put("placementSolutions", new JSONArray().put(solution1)) + .put("licenseSolutions", new JSONArray())); + + oofHoming.processSolution(execution, asyncResponse.toString()); + + ServiceInstance si = + execution.getGeneralBuildingBlock().getCustomer().getServiceSubscription().getServiceInstances().get(0); + + assertFalse(si.getVpnBondingLinks().isEmpty()); + VpnBondingLink link = si.getVpnBondingLinks().get(0); + assertNotNull(link); + assertFalse(link.getServiceProxies().isEmpty()); + + assertEquals("testServiceInstanceId1", + link.getServiceProxy("testProxyId1").getServiceInstance().getServiceInstanceId()); + assertNotNull(link.getServiceProxy("testProxyId1").getServiceInstance().getSolutionInfo()); + assertEquals("testVnfHostName1", + link.getServiceProxy("testProxyId1").getServiceInstance().getVnfs().get(0).getVnfName()); + + assertEquals("testServiceInstanceId2", + link.getServiceProxy("testProxyId2").getServiceInstance().getServiceInstanceId()); + assertNotNull(link.getServiceProxy("testProxyId2").getServiceInstance().getSolutionInfo()); + assertFalse(link.getServiceProxy("testProxyId2").getServiceInstance().getPnfs().isEmpty()); + assertEquals("testPrimaryPnfName2", + link.getServiceProxy("testProxyId2").getServiceInstance().getPnfs().get(0).getPnfName()); + assertEquals("primary", link.getServiceProxy("testProxyId2").getServiceInstance().getPnfs().get(0).getRole()); + assertEquals("testSecondaryPnfName2", + link.getServiceProxy("testProxyId2").getServiceInstance().getPnfs().get(1).getPnfName()); + assertEquals("secondary", link.getServiceProxy("testProxyId2").getServiceInstance().getPnfs().get(1).getRole()); + } + + @Test + public void testProcessSolution_success_1VpnLink_2Solutions() { + beforeVpnBondingLink("1"); + + JSONObject asyncResponse = new JSONObject(); + asyncResponse.put("transactionId", "transactionId").put("requestId", "testRequestId").put("requestState", + "completed"); + JSONArray solution1 = new JSONArray(); + solution1.put(new JSONObject().put("serviceResourceId", "testProxyId1") + .put("solution", + new JSONObject().put("identifierType", "serviceInstanceId").put("identifiers", + new JSONArray().put("testServiceInstanceId1"))) + .put("assignmentInfo", + new JSONArray().put(new JSONObject().put("key", "isRehome").put("value", "False")) + .put(new JSONObject().put("key", "cloudOwner").put("value", "aic")) + .put(new JSONObject().put("key", "vnfHostName").put("value", "testVnfHostName1")) + .put(new JSONObject().put("key", "aicClli").put("value", "testAicClli1")) + .put(new JSONObject().put("key", "aicVersion").put("value", "3")) + .put(new JSONObject().put("key", "vnfId").put("value", "testVnfId1")) + .put(new JSONObject().put("key", "cloudRegionId").put("value", "testSloudRegionId1")))); + solution1.put(new JSONObject().put("serviceResourceId", "testProxyId2") + .put("solution", + new JSONObject().put("identifierType", "serviceInstanceId").put("identifiers", + new JSONArray().put("testServiceInstanceId2"))) + .put("assignmentInfo", + new JSONArray().put(new JSONObject().put("key", "isRehome").put("value", "False")) + .put(new JSONObject().put("key", "cloudOwner").put("value", "aic")) + .put(new JSONObject().put("key", "primaryPnfName").put("value", "testPrimaryPnfName2")) + .put(new JSONObject().put("key", "aicClli").put("value", "testAicClli2")) + .put(new JSONObject().put("key", "aicVersion").put("value", "3")) + .put(new JSONObject().put("key", "secondaryPnfName").put("value", + "testSecondaryPnfName2")) + .put(new JSONObject().put("key", "cloudRegionId").put("value", "testSloudRegionId2")))); + + JSONArray solution2 = new JSONArray(); + solution2.put(new JSONObject().put("serviceResourceId", "testProxyId1") + .put("solution", + new JSONObject().put("identifierType", "serviceInstanceId").put("identifiers", + new JSONArray().put("testServiceInstanceId3"))) + .put("assignmentInfo", + new JSONArray().put(new JSONObject().put("key", "isRehome").put("value", "False")) + .put(new JSONObject().put("key", "cloudOwner").put("value", "aic")) + .put(new JSONObject().put("key", "vnfHostName").put("value", "testVnfHostName3")) + .put(new JSONObject().put("key", "aicClli").put("value", "testAicClli3")) + .put(new JSONObject().put("key", "aicVersion").put("value", "3")) + .put(new JSONObject().put("key", "vnfId").put("value", "testVnfId3")) + .put(new JSONObject().put("key", "cloudRegionId").put("value", "testSloudRegionId3")))); + solution2.put(new JSONObject().put("serviceResourceId", "testProxyId2") + .put("solution", + new JSONObject().put("identifierType", "serviceInstanceId").put("identifiers", + new JSONArray().put("testServiceInstanceId4"))) + .put("assignmentInfo", + new JSONArray().put(new JSONObject().put("key", "isRehome").put("value", "False")) + .put(new JSONObject().put("key", "cloudOwner").put("value", "aic")) + .put(new JSONObject().put("key", "primaryPnfName").put("value", "testPrimaryPnfName4")) + .put(new JSONObject().put("key", "aicClli").put("value", "testAicClli4")) + .put(new JSONObject().put("key", "aicVersion").put("value", "3")) + .put(new JSONObject().put("key", "secondaryPnfName").put("value", + "testSecondaryPnfName4")) + .put(new JSONObject().put("key", "cloudRegionId").put("value", "testSloudRegionId4")))); + + asyncResponse.put("solutions", + new JSONObject().put("placementSolutions", new JSONArray().put(solution1).put(solution2)) + .put("licenseSolutions", new JSONArray())); + + oofHoming.processSolution(execution, asyncResponse.toString()); + + ServiceInstance si = + execution.getGeneralBuildingBlock().getCustomer().getServiceSubscription().getServiceInstances().get(0); + + assertFalse(si.getVpnBondingLinks().isEmpty()); + VpnBondingLink link = si.getVpnBondingLinks().get(0); + VpnBondingLink link2 = si.getVpnBondingLinks().get(1); + assertNotNull(link); + assertFalse(link.getServiceProxies().isEmpty()); + + assertEquals("testServiceInstanceId1", + link.getServiceProxy("testProxyId1").getServiceInstance().getServiceInstanceId()); + assertNotNull(link.getServiceProxy("testProxyId1").getServiceInstance().getSolutionInfo()); + assertEquals("testVnfHostName1", + link.getServiceProxy("testProxyId1").getServiceInstance().getVnfs().get(0).getVnfName()); + + assertEquals("testServiceInstanceId2", + link.getServiceProxy("testProxyId2").getServiceInstance().getServiceInstanceId()); + assertNotNull(link.getServiceProxy("testProxyId2").getServiceInstance().getSolutionInfo()); + assertFalse(link.getServiceProxy("testProxyId2").getServiceInstance().getPnfs().isEmpty()); + assertEquals("testPrimaryPnfName2", + link.getServiceProxy("testProxyId2").getServiceInstance().getPnfs().get(0).getPnfName()); + assertEquals("primary", link.getServiceProxy("testProxyId2").getServiceInstance().getPnfs().get(0).getRole()); + assertEquals("testSecondaryPnfName2", + link.getServiceProxy("testProxyId2").getServiceInstance().getPnfs().get(1).getPnfName()); + assertEquals("secondary", link.getServiceProxy("testProxyId2").getServiceInstance().getPnfs().get(1).getRole()); + + assertNotNull(link2); + assertFalse(link2.getServiceProxies().isEmpty()); + + assertEquals("testServiceInstanceId3", + link2.getServiceProxy("testProxyId1").getServiceInstance().getServiceInstanceId()); + assertNotNull(link2.getServiceProxy("testProxyId1").getServiceInstance().getSolutionInfo()); + assertEquals("testVnfHostName3", + link2.getServiceProxy("testProxyId1").getServiceInstance().getVnfs().get(0).getVnfName()); + + assertEquals("testServiceInstanceId4", + link2.getServiceProxy("testProxyId2").getServiceInstance().getServiceInstanceId()); + assertNotNull(link2.getServiceProxy("testProxyId2").getServiceInstance().getSolutionInfo()); + assertFalse(link2.getServiceProxy("testProxyId2").getServiceInstance().getPnfs().isEmpty()); + assertEquals("testPrimaryPnfName4", + link2.getServiceProxy("testProxyId2").getServiceInstance().getPnfs().get(0).getPnfName()); + assertEquals("primary", link2.getServiceProxy("testProxyId2").getServiceInstance().getPnfs().get(0).getRole()); + assertEquals("testSecondaryPnfName4", + link2.getServiceProxy("testProxyId2").getServiceInstance().getPnfs().get(1).getPnfName()); + assertEquals("secondary", + link2.getServiceProxy("testProxyId2").getServiceInstance().getPnfs().get(1).getRole()); + + } + + @Test + public void testProcessSolution_success_3VpnLink_2Solutions() { + // TODO + } + + @Test + public void testProcessSolution_success_3Allotteds_1Solution() { + beforeAllottedResource(); + + JSONObject asyncResponse = new JSONObject(); + asyncResponse.put("transactionId", "transactionId").put("requestId", "testRequestId").put("requestState", + "completed"); + JSONArray solution1 = new JSONArray(); + solution1.put(new JSONObject().put("serviceResourceId", "testAllottedResourceId1") + .put("solution", + new JSONObject().put("identifierType", "serviceInstanceId").put("identifiers", + new JSONArray().put("testServiceInstanceId1"))) + .put("assignmentInfo", + new JSONArray().put(new JSONObject().put("key", "isRehome").put("value", "True")) + .put(new JSONObject().put("key", "cloudOwner").put("value", "aic")) + .put(new JSONObject().put("key", "vnfHostName").put("value", "testVnfHostName1")) + .put(new JSONObject().put("key", "aicClli").put("value", "testAicClli1")) + .put(new JSONObject().put("key", "aicVersion").put("value", "3")) + .put(new JSONObject().put("key", "vnfId").put("value", "testVnfId1")) + .put(new JSONObject().put("key", "cloudRegionId").put("value", "testCloudRegionId1")))); + solution1.put(new JSONObject().put("serviceResourceId", "testAllottedResourceId2") + .put("solution", + new JSONObject().put("identifierType", "serviceInstanceId").put("identifiers", + new JSONArray().put("testServiceInstanceId2"))) + .put("assignmentInfo", + new JSONArray().put(new JSONObject().put("key", "isRehome").put("value", "True")) + .put(new JSONObject().put("key", "cloudOwner").put("value", "aic")) + .put(new JSONObject().put("key", "vnfHostName").put("value", "testVnfHostName2")) + .put(new JSONObject().put("key", "aicClli").put("value", "testAicClli2")) + .put(new JSONObject().put("key", "aicVersion").put("value", "3")) + .put(new JSONObject().put("key", "vnfId").put("value", "testVnfId1")) + .put(new JSONObject().put("key", "cloudRegionId").put("value", "testCloudRegionId2")))); + solution1.put(new JSONObject().put("serviceResourceId", "testAllottedResourceId3") + .put("solution", + new JSONObject().put("identifierType", "cloudRegionId").put("identifiers", + new JSONArray().put("testCloudRegionId3"))) + .put("assignmentInfo", + new JSONArray().put(new JSONObject().put("key", "isRehome").put("value", "True")) + .put(new JSONObject().put("key", "cloudOwner").put("value", "aic")) + .put(new JSONObject().put("key", "aicClli").put("value", "testAicClli2")) + .put(new JSONObject().put("key", "aicVersion").put("value", "3")))); + + asyncResponse.put("solutions", new JSONObject().put("placementSolutions", new JSONArray().put(solution1)) + .put("licenseSolutions", new JSONArray())); + + oofHoming.processSolution(execution, asyncResponse.toString()); + + ServiceInstance si = + execution.getGeneralBuildingBlock().getCustomer().getServiceSubscription().getServiceInstances().get(0); + + assertFalse(si.getAllottedResources().isEmpty()); + AllottedResource ar = si.getAllottedResources().get(0); + assertNotNull(ar); + assertEquals("testServiceInstanceId1", ar.getParentServiceInstance().getServiceInstanceId()); + assertNotNull(ar.getParentServiceInstance().getSolutionInfo()); + assertEquals("testVnfHostName1", ar.getParentServiceInstance().getVnfs().get(0).getVnfName()); + + AllottedResource ar2 = si.getAllottedResources().get(1); + assertNotNull(ar2); + assertEquals("testServiceInstanceId2", ar2.getParentServiceInstance().getServiceInstanceId()); + assertNotNull(ar2.getParentServiceInstance().getSolutionInfo()); + assertEquals("testVnfHostName2", ar2.getParentServiceInstance().getVnfs().get(0).getVnfName()); + + AllottedResource ar3 = si.getAllottedResources().get(2); + assertNotNull(ar3); + assertNotNull(ar3.getParentServiceInstance().getSolutionInfo()); + assertEquals("testCloudRegionId3", + ar3.getParentServiceInstance().getSolutionInfo().getTargetedCloudRegion().getLcpCloudRegionId()); + } + + @Test + public void testProcessSolution_success_3Allotteds1Vnf_1Solution() { + beforeVnf(); + beforeAllottedResource(); + + JSONObject asyncResponse = new JSONObject(); + asyncResponse.put("transactionId", "transactionId").put("requestId", "testRequestId").put("requestState", + "completed"); + JSONArray solution1 = new JSONArray(); + JSONArray licenseSolution = new JSONArray(); + solution1.put(new JSONObject().put("serviceResourceId", "testAllottedResourceId1") + .put("solution", + new JSONObject().put("identifierType", "serviceInstanceId").put("identifiers", + new JSONArray().put("testServiceInstanceId1"))) + .put("assignmentInfo", + new JSONArray().put(new JSONObject().put("key", "isRehome").put("value", "True")) + .put(new JSONObject().put("key", "cloudOwner").put("value", "aic")) + .put(new JSONObject().put("key", "vnfHostName").put("value", "testVnfHostName1")) + .put(new JSONObject().put("key", "aicClli").put("value", "testAicClli1")) + .put(new JSONObject().put("key", "aicVersion").put("value", "3")) + .put(new JSONObject().put("key", "vnfId").put("value", "testVnfId1")) + .put(new JSONObject().put("key", "cloudRegionId").put("value", "testCloudRegionId1")))); + solution1.put(new JSONObject().put("serviceResourceId", "testAllottedResourceId2") + .put("solution", + new JSONObject().put("identifierType", "serviceInstanceId").put("identifiers", + new JSONArray().put("testServiceInstanceId2"))) + .put("assignmentInfo", + new JSONArray().put(new JSONObject().put("key", "isRehome").put("value", "True")) + .put(new JSONObject().put("key", "cloudOwner").put("value", "aic")) + .put(new JSONObject().put("key", "vnfHostName").put("value", "testVnfHostName2")) + .put(new JSONObject().put("key", "aicClli").put("value", "testAicClli2")) + .put(new JSONObject().put("key", "aicVersion").put("value", "3")) + .put(new JSONObject().put("key", "vnfId").put("value", "testVnfId1")) + .put(new JSONObject().put("key", "cloudRegionId").put("value", "testCloudRegionId2")))); + solution1.put(new JSONObject().put("serviceResourceId", "testAllottedResourceId3") + .put("solution", + new JSONObject().put("identifierType", "cloudRegionId").put("identifiers", + new JSONArray().put("testCloudRegionId3"))) + .put("assignmentInfo", + new JSONArray().put(new JSONObject().put("key", "isRehome").put("value", "True")) + .put(new JSONObject().put("key", "cloudOwner").put("value", "aic")) + .put(new JSONObject().put("key", "aicClli").put("value", "testAicClli2")) + .put(new JSONObject().put("key", "aicVersion").put("value", "3")))); + + licenseSolution.put(new JSONObject().put("serviceResourceId", "testVnfId1") + .put("entitlementPoolUUID", + new JSONArray().put("f1d563e8-e714-4393-8f99-cc480144a05e") + .put("j1d563e8-e714-4393-8f99-cc480144a05e")) + .put("licenseKeyGroupUUID", new JSONArray().put("s1d563e8-e714-4393-8f99-cc480144a05e") + .put("b1d563e8-e714-4393-8f99-cc480144a05e"))); + + asyncResponse.put("solutions", new JSONObject().put("placementSolutions", new JSONArray().put(solution1)) + .put("licenseSolutions", licenseSolution)); + + oofHoming.processSolution(execution, asyncResponse.toString()); + + ServiceInstance si = + execution.getGeneralBuildingBlock().getCustomer().getServiceSubscription().getServiceInstances().get(0); + + assertFalse(si.getAllottedResources().isEmpty()); + AllottedResource ar = si.getAllottedResources().get(0); + assertNotNull(ar); + assertEquals("testServiceInstanceId1", ar.getParentServiceInstance().getServiceInstanceId()); + assertNotNull(ar.getParentServiceInstance().getSolutionInfo()); + assertEquals("testVnfHostName1", ar.getParentServiceInstance().getVnfs().get(0).getVnfName()); + + AllottedResource ar2 = si.getAllottedResources().get(1); + assertNotNull(ar2); + assertEquals("testServiceInstanceId2", ar2.getParentServiceInstance().getServiceInstanceId()); + assertNotNull(ar2.getParentServiceInstance().getSolutionInfo()); + assertEquals("testVnfHostName2", ar2.getParentServiceInstance().getVnfs().get(0).getVnfName()); + + AllottedResource ar3 = si.getAllottedResources().get(2); + assertNotNull(ar3); + assertNotNull(ar3.getParentServiceInstance().getSolutionInfo()); + assertEquals("testCloudRegionId3", + ar3.getParentServiceInstance().getSolutionInfo().getTargetedCloudRegion().getLcpCloudRegionId()); + + GenericVnf vnf = si.getVnfs().get(0); + assertNotNull(vnf); + assertNotNull(vnf.getLicense()); + assertEquals(2, vnf.getLicense().getEntitlementPoolUuids().size()); + assertEquals("s1d563e8-e714-4393-8f99-cc480144a05e", vnf.getLicense().getLicenseKeyGroupUuids().get(0)); + + } + + @Test + public void testProcessSolution_success_1Vnf_1Solution() { + beforeVnf(); + + JSONObject asyncResponse = new JSONObject(); + asyncResponse.put("transactionId", "transactionId").put("requestId", "testRequestId").put("requestState", + "completed"); + JSONArray licenseSolution = new JSONArray(); + + licenseSolution.put(new JSONObject().put("serviceResourceId", "testVnfId1") + .put("entitlementPoolUUID", + new JSONArray().put("f1d563e8-e714-4393-8f99-cc480144a05e") + .put("j1d563e8-e714-4393-8f99-cc480144a05e")) + .put("licenseKeyGroupUUID", new JSONArray().put("s1d563e8-e714-4393-8f99-cc480144a05e") + .put("b1d563e8-e714-4393-8f99-cc480144a05e"))); + + asyncResponse.put("solutions", new JSONObject().put("licenseSolutions", licenseSolution)); + + oofHoming.processSolution(execution, asyncResponse.toString()); + + ServiceInstance si = + execution.getGeneralBuildingBlock().getCustomer().getServiceSubscription().getServiceInstances().get(0); + + GenericVnf vnf = si.getVnfs().get(0); + assertNotNull(vnf); + assertNotNull(vnf.getLicense()); + assertEquals(2, vnf.getLicense().getEntitlementPoolUuids().size()); + assertEquals(2, vnf.getLicense().getLicenseKeyGroupUuids().size()); + assertEquals("f1d563e8-e714-4393-8f99-cc480144a05e", vnf.getLicense().getEntitlementPoolUuids().get(0)); + assertEquals("s1d563e8-e714-4393-8f99-cc480144a05e", vnf.getLicense().getLicenseKeyGroupUuids().get(0)); + + + } + + @Test(expected = BpmnError.class) + public void testCallOof_error_0Resources() throws BadResponseException, JsonProcessingException { + + oofHoming.callOof(execution); + + verify(oofClient, times(0)).postDemands(isA(OofRequest.class)); + } + + @Test(expected = BpmnError.class) + public void testCallOof_error_badResponse() throws BadResponseException, JsonProcessingException { + beforeAllottedResource(); + + mockResponse = + "{\"transactionId\": \"123456789\", \"requestId\": \"1234\", \"statusMessage\": \"\", \"requestStatus\": \"failed\"}"; + wireMockServer.stubFor(post(urlEqualTo("/api/oof/v1/placement")).willReturn( + aResponse().withStatus(200).withHeader("Content-Type", "application/json").withBody(mockResponse))); + + oofHoming.callOof(execution); + + verify(oofClient, times(1)).postDemands(isA(OofRequest.class)); + } + +} diff --git a/bpmn/so-bpmn-tasks/src/test/java/org/onap/so/client/oof/OofClientTestIT.java b/bpmn/so-bpmn-tasks/src/test/java/org/onap/so/client/oof/OofClientTestIT.java index ce7d557254..3ae0db627a 100644 --- a/bpmn/so-bpmn-tasks/src/test/java/org/onap/so/client/oof/OofClientTestIT.java +++ b/bpmn/so-bpmn-tasks/src/test/java/org/onap/so/client/oof/OofClientTestIT.java @@ -27,6 +27,7 @@ import static org.junit.Assert.assertEquals; import org.junit.Test; import org.onap.so.BaseIntegrationTest; import org.onap.so.client.exception.BadResponseException; +import org.onap.so.client.oof.beans.LicenseInfo; import org.onap.so.client.oof.beans.ModelInfo; import org.onap.so.client.oof.beans.OofRequest; import org.onap.so.client.oof.beans.OofRequestParameters; @@ -36,6 +37,7 @@ import org.onap.so.client.oof.beans.RequestInfo; import org.onap.so.client.oof.beans.ResourceModelInfo; import org.onap.so.client.oof.beans.ServiceInfo; import org.onap.so.client.oof.beans.SubscriberInfo; +import org.skyscreamer.jsonassert.JSONAssert; import org.springframework.beans.factory.annotation.Autowired; import com.fasterxml.jackson.core.JsonProcessingException; import java.util.ArrayList; @@ -114,7 +116,7 @@ public class OofClientTestIT extends BaseIntegrationTest { oofRequest.setRequestInformation(requestInfo); oofRequest.setPlacementInformation(placementInfo); oofRequest.setServiceInformation(serviceInfo); - oofRequest.setLicenseInformation(""); + oofRequest.setLicenseInformation(new LicenseInfo()); wireMockServer.stubFor(post(urlEqualTo("/api/oof/v1/placement")).willReturn( aResponse().withStatus(200).withHeader("Content-Type", "application/json").withBody(mockResponse))); @@ -122,7 +124,7 @@ public class OofClientTestIT extends BaseIntegrationTest { client.postDemands(oofRequest); String oofRequestOutput = oofRequest.toJsonString(); - assertEquals("{\n" + " \"requestInfo\" : {\n" + " \"transactionId\" : \"transactionId\",\n" + JSONAssert.assertEquals("{\n" + " \"requestInfo\" : {\n" + " \"transactionId\" : \"transactionId\",\n" + " \"requestId\" : \"requestId\",\n" + " \"callbackUrl\" : \"callBackUrl\",\n" + " \"sourceId\" : \"sourceId\",\n" + " \"requestType\" : \"requestType\",\n" + " \"numSolutions\" : 1,\n" + " \"optimizers\" : [ \"optimizer1\", \"optimizer2\" ],\n" @@ -147,7 +149,8 @@ public class OofClientTestIT extends BaseIntegrationTest { + " \"modelInvariantId\" : \"invarianteId\",\n" + " \"modelVersionId\" : \"versionId\",\n" + " \"modelName\" : \"modelName\",\n" + " \"modelVersion\" : \"version\",\n" + " \"modelCustomizationName\" : \"modelCustomizationName\"\n" + " }\n" + " } ]\n" - + " },\n" + " \"licenseInfo\" : \"\"\n" + "}", oofRequestOutput); + + " },\n" + " \"licenseInfo\" : { \n" + " \"licenseDemands\" : [ ]\n" + "}\n" + "}", + oofRequestOutput, false); } @Test diff --git a/bpmn/so-bpmn-tasks/src/test/resources/__files/BuildingBlocks/OofHoming/oofRequest1Vpn.json b/bpmn/so-bpmn-tasks/src/test/resources/__files/BuildingBlocks/OofHoming/oofRequest1Vpn.json new file mode 100644 index 0000000000..99ce7fb2bf --- /dev/null +++ b/bpmn/so-bpmn-tasks/src/test/resources/__files/BuildingBlocks/OofHoming/oofRequest1Vpn.json @@ -0,0 +1,58 @@ +{ + "requestInfo":{ + "transactionId":"requestId", + "requestId":"requestId", + "callbackUrl":"http://localhost:28080/mso/WorkflowMesssage/OofResponse/requestId", + "sourceId":"mso", + "requestType":"create", + "numSolutions":1, + "optimizers":[ "placement" ], + "timeout":1800 + }, + "serviceInfo":{ + "serviceInstanceId":"testServiceInstanceId1", + "serviceName":"testServiceType1", + "modelInfo":{ + "modelInvariantId":"testModelInvariantUUID1", + "modelVersionId":"testModelUUID1", + "modelName":"testModelName1", + "modelVersion":"testModelVersion1" + } + }, + "placementInfo":{ + "requestParameters":{ + "customerLatitude":"customerLatitude", + "customerLongitude":"customerLongitude", + "customerName":"testCustomerName" + }, + "subscriberInfo":{ + "globalSubscriberId":"testCustomerId", + "subscriberName":"testCustomerName" + }, + "placementDemands":[ + { + "resourceModuleName":"testProxyInstanceName1", + "serviceResourceId":"testProxyId1", + "resourceModelInfo":{ + "modelInvariantId":"testProxyModelInvariantUuid1", + "modelVersionId":"testProxyModelUuid1", + "modelName":"testProxyModelName1", + "modelVersion":"testProxyModelVersion1" + } + }, + { + "resourceModuleName":"testProxyInstanceName2", + "serviceResourceId":"testProxyId2", + "resourceModelInfo":{ + "modelInvariantId":"testProxyModelInvariantUuid2", + "modelVersionId":"testProxyModelUuid2", + "modelName":"testProxyModelName2", + "modelVersion":"testProxyModelVersion2" + } + } + ] + }, + "licenseInfo":{ + "licenseDemands" : [ ] + } +} diff --git a/bpmn/so-bpmn-tasks/src/test/resources/__files/BuildingBlocks/OofHoming/oofRequest1VpnOpt.json b/bpmn/so-bpmn-tasks/src/test/resources/__files/BuildingBlocks/OofHoming/oofRequest1VpnOpt.json new file mode 100644 index 0000000000..d149b328df --- /dev/null +++ b/bpmn/so-bpmn-tasks/src/test/resources/__files/BuildingBlocks/OofHoming/oofRequest1VpnOpt.json @@ -0,0 +1,61 @@ +{ + "requestInfo" : { + "transactionId" : "requestId", + "requestId" : "requestId", + "callbackUrl" : "http://localhost:28080/mso/WorkflowMesssage/OofResponse/requestId", + "sourceId" : "mso", + "requestType" : "create", + "numSolutions" : 1, + "optimizers" : [ "placement" ], + "timeout" : 1800 + }, + "serviceInfo" : { + "serviceInstanceId" : "serviceInstanceId", + "serviceName" : "serviceName", + "modelInfo" : { + "modelType" : "modelType-Service", + "modelInvariantId" : "modelInvariantId-Service", + "modelVersionId" : "modelVersionId-Service", + "modelName" : "modelName-Service", + "modelVersion" : "modelVersion-Service", + "modelCustomizationName" : "modelCustomizationName-Service" + } + }, + "placementInfo" : { + "requestParameters" : { + "customerLatitude" : "customerLatitude", + "customerLongitude" : "customerLongitude", + "customerName" : "customerName" + }, + "subscriberInfo" : { + "globalSubscriberId" : "globalSubscriberId", + "subscriberName" : "subscriberName" + }, + "placementDemands" : [ { + "serviceResourceId" : "testProxyId1", + "resourceModuleName" : "testProxyInstanceName1", + "resourceModelInfo" : { + "modelName" : "testProxyModelName1", + "modelVersionId" : "testProxyModelUuid1", + "modelVersion" : "testProxyModelVersion1", + "modelInvariantId" : "testProxyModelInvariantUuid1" + } + }, { + "serviceResourceId" : "testProxyId2", + "resourceModuleName" : "testProxyInstanceName2", + "resourceModelInfo" : { + "modelName" : "testProxyModelName2", + "modelVersionId" : "testProxyModelUuid2", + "modelVersion" : "testProxyModelVersion2", + "modelInvariantId" : "testProxyModelInvariantUuid2" + }, + "requiredCandidates" : [ { + "identifierType" : "vnfId", + "identifiers" : [ "testVnfId" ] + } ] + } ] + }, + "licenseInfo" : { + "licenseDemands" : [ ] + } +} diff --git a/bpmn/so-bpmn-tasks/src/test/resources/__files/BuildingBlocks/OofHoming/oofRequest3Ar.json b/bpmn/so-bpmn-tasks/src/test/resources/__files/BuildingBlocks/OofHoming/oofRequest3Ar.json new file mode 100644 index 0000000000..9b251aac9a --- /dev/null +++ b/bpmn/so-bpmn-tasks/src/test/resources/__files/BuildingBlocks/OofHoming/oofRequest3Ar.json @@ -0,0 +1,67 @@ +{ + "requestInfo" : { + "transactionId" : "requestId", + "requestId" : "requestId", + "callbackUrl" : "http://localhost:28080/mso/WorkflowMesssage/OofResponse/requestId", + "sourceId" : "mso", + "requestType" : "create", + "numSolutions" : 1, + "optimizers" : [ "placement" ], + "timeout" : 1800 + }, + "serviceInfo" : { + "serviceInstanceId" : "testServiceInstanceId1", + "serviceName" : "testServiceType1", + "modelInfo" : { + "modelInvariantId" : "testModelInvariantUUID1", + "modelVersionId" : "testModelUUID1", + "modelName" : "testModelName1", + "modelVersion" : "testModelVersion1" + } + }, + "placementInfo" : { + "requestParameters": { + "customerLatitude": "customerLatitude", + "customerLongitude": "customerLongitude", + "customerName": "testCustomerName" + }, + "subscriberInfo": { + "globalSubscriberId" : "testCustomerId", + "subscriberName" : "testCustomerName" + }, + "placementDemands": [ + { + "serviceResourceId": "testAllottedResourceId1", + "resourceModuleName": "testAllottedModelInstanceName1", + "resourceModelInfo": { + "modelName": "testAllottedModelName1", + "modelVersionId": "testAllottedModelUuid1", + "modelVersion": "testAllottedModelVersion1", + "modelInvariantId": "testAllottedModelInvariantUuid1" + } + }, + { + "serviceResourceId": "testAllottedResourceId2", + "resourceModuleName": "testAllottedModelInstanceName2", + "resourceModelInfo": { + "modelName": "testAllottedModelName2", + "modelVersionId": "testAllottedModelUuid2", + "modelVersion": "testAllottedModelVersion2", + "modelInvariantId": "testAllottedModelInvariantUuid2" + } + }, + { + "serviceResourceId": "testAllottedResourceId3", + "resourceModuleName": "testAllottedModelInstanceName3", + "resourceModelInfo": { + "modelName": "testAllottedModelName3", + "modelVersionId": "testAllottedModelUuid3", + "modelVersion": "testAllottedModelVersion3", + "modelInvariantId": "testAllottedModelInvariantUuid3" + } + } ] + }, + "licenseInfo" : { + "licenseDemands" : [ ] + } +} diff --git a/bpmn/so-bpmn-tasks/src/test/resources/__files/BuildingBlocks/OofHoming/oofRequest3Vpn.json b/bpmn/so-bpmn-tasks/src/test/resources/__files/BuildingBlocks/OofHoming/oofRequest3Vpn.json new file mode 100644 index 0000000000..e3a735df00 --- /dev/null +++ b/bpmn/so-bpmn-tasks/src/test/resources/__files/BuildingBlocks/OofHoming/oofRequest3Vpn.json @@ -0,0 +1,91 @@ +{ + "requestInfo" : { + "transactionId" : "requestId", + "requestId" : "requestId", + "callbackUrl" : "http://localhost:28080/mso/WorkflowMesssage/OofResponse/requestId", + "sourceId" : "mso", + "requestType" : "create", + "numSolutions" : 1, + "optimizers" : [ "placement" ], + "timeout" : 1800 + }, + "serviceInfo" : { + "serviceInstanceId" : "testServiceInstanceId1", + "serviceName" : "testServiceType1", + "modelInfo":{ + "modelInvariantId":"testModelInvariantUUID1", + "modelVersionId":"testModelUUID1", + "modelName":"testModelName1", + "modelVersion":"testModelVersion1" + } + }, + "placementInfo" : { + "requestParameters" : { + "customerLatitude" : "customerLatitude", + "customerLongitude" : "customerLongitude", + "customerName" : "testCustomerName" + }, + "subscriberInfo" : { + "globalSubscriberId" : "testCustomerId", + "subscriberName" : "testCustomerName" + }, + "placementDemands" : [ { + "resourceModuleName" : "testProxyInstanceName1", + "serviceResourceId" : "testProxyId1", + "resourceModelInfo" : { + "modelInvariantId" : "testProxyModelInvariantUuid1", + "modelVersionId" : "testProxyModelUuid1", + "modelName" : "testProxyModelName1", + "modelVersion" : "testProxyModelVersion1" + } + }, { + "resourceModuleName" : "testProxyInstanceName2", + "serviceResourceId" : "testProxyId2", + "resourceModelInfo" : { + "modelInvariantId" : "testProxyModelInvariantUuid2", + "modelVersionId" : "testProxyModelUuid2", + "modelName" : "testProxyModelName2", + "modelVersion" : "testProxyModelVersion2" + } + }, { + "resourceModuleName" : "testProxyInstanceName1", + "serviceResourceId" : "testProxyId1", + "resourceModelInfo" : { + "modelInvariantId" : "testProxyModelInvariantUuid1", + "modelVersionId" : "testProxyModelUuid1", + "modelName" : "testProxyModelName1", + "modelVersion" : "testProxyModelVersion1" + } + }, { + "resourceModuleName" : "testProxyInstanceName2", + "serviceResourceId" : "testProxyId2", + "resourceModelInfo" : { + "modelInvariantId" : "testProxyModelInvariantUuid2", + "modelVersionId" : "testProxyModelUuid2", + "modelName" : "testProxyModelName2", + "modelVersion" : "testProxyModelVersion2" + } + }, { + "resourceModuleName" : "testProxyInstanceName1", + "serviceResourceId" : "testProxyId1", + "resourceModelInfo" : { + "modelInvariantId" : "testProxyModelInvariantUuid1", + "modelVersionId" : "testProxyModelUuid1", + "modelName" : "testProxyModelName1", + "modelVersion" : "testProxyModelVersion1" + } + }, { + "resourceModuleName" : "testProxyInstanceName2", + "serviceResourceId" : "testProxyId2", + "resourceModelInfo" : { + "modelInvariantId" : "testProxyModelInvariantUuid2", + "modelVersionId" : "testProxyModelUuid2", + "modelName" : "testProxyModelName2", + "modelVersion" : "testProxyModelVersion2" + } + } ] + }, + "licenseInfo" : { + "licenseDemands" : [ ] + } +} -- cgit 1.2.3-korg