Support for selection of capabilities 36/121236/3
authorandre.schmid <andre.schmid@est.tech>
Wed, 5 May 2021 14:31:04 +0000 (15:31 +0100)
committerChristophe Closset <christophe.closset@intl.att.com>
Sat, 15 May 2021 06:21:13 +0000 (06:21 +0000)
Change-Id: Ib1a3e3e1a59fc84c62620932c408e231acf77024
Issue-ID: SDC-3580
Signed-off-by: André Schmid <andre.schmid@est.tech>
35 files changed:
catalog-be/src/main/docker/backend/chef-repo/cookbooks/sdc-catalog-be/files/default/error-configuration.yaml
catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ComponentInstanceBusinessLogic.java
catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/exceptions/ComponentException.java
catalog-be/src/main/java/org/openecomp/sdc/be/servlets/ComponentInstanceCapabilityServlet.java [new file with mode: 0644]
catalog-be/src/main/java/org/openecomp/sdc/be/servlets/ComponentInstanceServlet.java
catalog-be/src/main/java/org/openecomp/sdc/be/servlets/builder/ServletResponseBuilder.java [new file with mode: 0644]
catalog-be/src/main/java/org/openecomp/sdc/be/servlets/exception/OperationExceptionMapper.java [new file with mode: 0644]
catalog-be/src/main/java/org/openecomp/sdc/be/tosca/CapabilityRequirementConverter.java
catalog-be/src/main/java/org/openecomp/sdc/config/CatalogBESpringConfig.java
catalog-be/src/test/java/org/openecomp/sdc/be/components/impl/ComponentInstanceBusinessLogicTest.java
catalog-be/src/test/java/org/openecomp/sdc/be/servlets/ComponentInstanceCapabilityServletTest.java [new file with mode: 0644]
catalog-be/src/test/java/org/openecomp/sdc/be/servlets/JerseySpringBaseTest.java
catalog-be/src/test/java/org/openecomp/sdc/be/tosca/CapabilityRequirementConverterTest.java
catalog-be/src/test/resources/config/catalog-be/error-configuration.yaml
catalog-dao/src/main/java/org/openecomp/sdc/be/dao/api/ActionStatus.java
catalog-model/src/main/java/org/openecomp/sdc/be/model/jsonjanusgraph/operations/NodeTemplateOperation.java
catalog-model/src/main/java/org/openecomp/sdc/be/model/jsonjanusgraph/operations/ToscaOperationFacade.java
catalog-model/src/main/java/org/openecomp/sdc/be/model/jsonjanusgraph/operations/exception/OperationException.java [new file with mode: 0644]
catalog-model/src/main/java/org/openecomp/sdc/be/ui/mapper/CapabilityMapper.java [new file with mode: 0644]
catalog-model/src/main/java/org/openecomp/sdc/be/ui/model/ComponentInstanceCapabilityUpdateModel.java [new file with mode: 0644]
catalog-model/src/test/java/org/openecomp/sdc/be/model/jsonjanusgraph/operations/NodeTemplateOperationTest.java
catalog-model/src/test/java/org/openecomp/sdc/be/ui/mapper/CapabilityMapperTest.java [new file with mode: 0644]
catalog-ui/src/app/models/capability.ts
catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.ts
catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/req-capabilities-tab/req-capabilities-tab.component.html
catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/req-capabilities-tab/req-capabilities-tab.component.ts
catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/req-capabilities-tab/requirement-list/requirement-list.component.html
catalog-ui/src/app/ng2/services/component-instance-services/component-instance.service.spec.ts [new file with mode: 0644]
catalog-ui/src/app/ng2/services/component-instance-services/component-instance.service.ts
catalog-ui/src/app/utils/constants.ts
common-app-api/src/main/java/org/openecomp/sdc/exception/ResponseFormat.java
common-app-logging/src/main/java/org/openecomp/sdc/common/log/enums/LoggerSupportabilityActions.java
common-be/src/main/java/org/openecomp/sdc/be/datatypes/elements/CapabilityDataDefinition.java
common-be/src/main/java/org/openecomp/sdc/be/exception/BusinessException.java [new file with mode: 0644]
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionRequirementsCapabilitiesTab.java

index 3e1b16d..b49c111 100644 (file)
@@ -2437,3 +2437,31 @@ errors:
         message: '%1 is not yet supported',
         messageId: "SVC4139"
     }
+    #---------SVC4140------------------------------
+    # %1 - Component uid
+    COMPONENT_FIND_ERROR: {
+        code: 500,
+        message: "An unexpected error occurred while retrieving the component '%1'.",
+        messageId: "SVC4140"
+    }
+    #---------SVC4141------------------------------
+    # %1 - Component uid
+    COMPONENT_CAPABILITIES_FIND_ERROR: {
+        code: 500,
+        message: "An unexpected error occurred while retrieving the component '%1' capabilities.",
+        messageId: "SVC4141"
+    }
+    #---------SVC4142------------------------------
+    # %1 - Component uid or name
+    COMPONENT_NOT_FOUND: {
+        code: 404,
+        message: "Component '%1' was not found.",
+        messageId: "SVC4142"
+    }
+    #---------SVC4143------------------------------
+    # %1 - Capability name
+    COMPONENT_INSTANCE_CAPABILITY_UPDATE_ERROR: {
+        code: 500,
+        message: "An unexpected error occurred while updating the capability '%1'.",
+        messageId: "SVC4143"
+    }
\ No newline at end of file
index b602072..b8fabc3 100644 (file)
@@ -74,6 +74,7 @@ import org.openecomp.sdc.be.datatypes.enums.NodeTypeEnum;
 import org.openecomp.sdc.be.datatypes.enums.OriginTypeEnum;
 import org.openecomp.sdc.be.datatypes.enums.ResourceTypeEnum;
 import org.openecomp.sdc.be.datatypes.tosca.ToscaGetFunctionType;
+import org.openecomp.sdc.be.exception.BusinessException;
 import org.openecomp.sdc.be.impl.ComponentsUtils;
 import org.openecomp.sdc.be.impl.ForwardingPathUtils;
 import org.openecomp.sdc.be.impl.ServiceFilterUtils;
@@ -108,6 +109,7 @@ import org.openecomp.sdc.be.model.jsonjanusgraph.operations.ArtifactsOperations;
 import org.openecomp.sdc.be.model.jsonjanusgraph.operations.ForwardingPathOperation;
 import org.openecomp.sdc.be.model.jsonjanusgraph.operations.InterfaceOperation;
 import org.openecomp.sdc.be.model.jsonjanusgraph.operations.NodeFilterOperation;
+import org.openecomp.sdc.be.model.jsonjanusgraph.operations.NodeTemplateOperation;
 import org.openecomp.sdc.be.model.jsonjanusgraph.operations.ToscaOperationFacade;
 import org.openecomp.sdc.be.model.jsonjanusgraph.utils.ModelConverter;
 import org.openecomp.sdc.be.model.operations.StorageException;
@@ -130,6 +132,8 @@ import org.openecomp.sdc.common.api.Constants;
 import org.openecomp.sdc.common.datastructure.Wrapper;
 import org.openecomp.sdc.common.jsongraph.util.CommonUtility;
 import org.openecomp.sdc.common.jsongraph.util.CommonUtility.LogLevelEnum;
+import org.openecomp.sdc.common.log.elements.ErrorLogOptionalData;
+import org.openecomp.sdc.common.log.enums.EcompLoggerErrorCode;
 import org.openecomp.sdc.common.log.wrappers.Logger;
 import org.openecomp.sdc.common.util.ValidationUtils;
 import org.openecomp.sdc.exception.ResponseFormat;
@@ -153,6 +157,8 @@ public class ComponentInstanceBusinessLogic extends BaseBusinessLogic {
     private static final String FAILED_TO_COPY_COMP_INSTANCE_TO_CANVAS = "Failed to copy the component instance to the canvas";
     private static final String COPY_COMPONENT_INSTANCE_OK = "Copy component instance OK";
     private static final String CANNOT_ATTACH_RESOURCE_INSTANCES_TO_CONTAINER_RESOURCE_OF_TYPE = "Cannot attach resource instances to container resource of type {}";
+    private static final String FAILED_TO_UPDATE_COMPONENT_INSTANCE_CAPABILITY = "Failed to update component instance capability on instance {} in "
+        + "container {}";
     private static final String SERVICE_PROXY = "serviceProxy";
     private static final String ASSOCIATE_RI_TO_RI = "associateRIToRI";
     private ComponentInstanceOperation componentInstanceOperation;
@@ -3238,6 +3244,69 @@ public class ComponentInstanceBusinessLogic extends BaseBusinessLogic {
         }
     }
 
+    public Either<CapabilityDefinition, ResponseFormat> updateInstanceCapability(final ComponentTypeEnum containerComponentType,
+                                                                                 final String containerComponentId,
+                                                                                 final String componentInstanceUniqueId,
+                                                                                 final CapabilityDefinition capabilityDefinition,
+                                                                                 final String userId) {
+        if (containerComponentType == null) {
+            BeEcompErrorManager.getInstance().logInvalidInputError("updateInstanceCapability", INVALID_COMPONENT_TYPE, ErrorSeverity.INFO);
+            return Either.right(componentsUtils.getResponseFormat(ActionStatus.NOT_ALLOWED));
+        }
+        validateUserExists(userId);
+        final Either<Component, StorageOperationStatus> getResourceResult = toscaOperationFacade.getToscaFullElement(containerComponentId);
+        if (getResourceResult.isRight()) {
+            log.debug(FAILED_TO_RETRIEVE_COMPONENT_COMPONENT_ID, containerComponentId);
+            return Either.right(componentsUtils.getResponseFormat(ActionStatus.COMPONENT_NOT_FOUND, containerComponentId));
+        }
+        final Component containerComponent = getResourceResult.left().value();
+        if (!ComponentValidationUtils.canWorkOnComponent(containerComponent, userId)) {
+            log.info("Restricted operation for user: {} on component {}", userId, containerComponentId);
+            return Either.right(componentsUtils.getResponseFormat(ActionStatus.RESTRICTED_OPERATION));
+        }
+        final Either<ComponentInstance, StorageOperationStatus> resourceInstanceStatus =
+            getResourceInstanceById(containerComponent, componentInstanceUniqueId);
+        if (resourceInstanceStatus.isRight()) {
+            return Either.right(componentsUtils
+                .getResponseFormat(ActionStatus.RESOURCE_INSTANCE_NOT_FOUND_ON_SERVICE, componentInstanceUniqueId, containerComponentId));
+        }
+        // lock resource
+        final StorageOperationStatus lockStatus = graphLockOperation.lockComponent(containerComponentId, containerComponentType.getNodeType());
+        if (lockStatus != StorageOperationStatus.OK) {
+            log.debug("Failed to lock component {}", containerComponentId);
+            return Either.right(componentsUtils.getResponseFormat(componentsUtils.convertFromStorageResponse(lockStatus)));
+        }
+        var success = false;
+        try {
+            final CapabilityDataDefinition updatedCapabilityDefinition = toscaOperationFacade
+                .updateComponentInstanceCapability(containerComponentId, componentInstanceUniqueId, capabilityDefinition);
+            final Either<Component, StorageOperationStatus> updateContainerEither = toscaOperationFacade
+                .updateComponentInstanceMetadataOfTopologyTemplate(containerComponent);
+            if (updateContainerEither.isRight()) {
+                var actionStatus = componentsUtils.convertFromStorageResponse(updateContainerEither.right().value(), containerComponentType);
+                return Either.right(componentsUtils.getResponseFormat(actionStatus));
+            }
+            success = true;
+            return Either.left(new CapabilityDefinition(updatedCapabilityDefinition));
+        } catch (final BusinessException e) {
+            log.error(EcompLoggerErrorCode.BUSINESS_PROCESS_ERROR, NodeTemplateOperation.class.getName(), (ErrorLogOptionalData) null,
+                FAILED_TO_UPDATE_COMPONENT_INSTANCE_CAPABILITY, componentInstanceUniqueId, containerComponentId);
+            throw e;
+        } catch (final Exception e) {
+            log.error(EcompLoggerErrorCode.BUSINESS_PROCESS_ERROR, NodeTemplateOperation.class.getName(), (ErrorLogOptionalData) null,
+                FAILED_TO_UPDATE_COMPONENT_INSTANCE_CAPABILITY, componentInstanceUniqueId, containerComponentId);
+            throw new ByResponseFormatComponentException(componentsUtils.getResponseFormat(ActionStatus.GENERAL_ERROR));
+        } finally {
+            if (success) {
+                janusGraphDao.commit();
+            } else {
+                janusGraphDao.rollback();
+            }
+            // unlock resource
+            graphLockOperation.unlockComponent(containerComponentId, containerComponentType.getNodeType());
+        }
+    }
+
     public Either<List<ComponentInstanceProperty>, ResponseFormat> updateInstanceCapabilityProperties(ComponentTypeEnum componentTypeEnum,
                                                                                                       String containerComponentId,
                                                                                                       String componentInstanceUniqueId,
index 351d279..7410e88 100644 (file)
@@ -22,10 +22,11 @@ package org.openecomp.sdc.be.components.impl.exceptions;
 import javax.annotation.Nullable;
 import org.openecomp.sdc.be.components.impl.ResponseFormatManager;
 import org.openecomp.sdc.be.dao.api.ActionStatus;
+import org.openecomp.sdc.be.exception.BusinessException;
 import org.openecomp.sdc.be.model.Resource;
 import org.openecomp.sdc.exception.ResponseFormat;
 
-public class ComponentException extends RuntimeException {
+public class ComponentException extends BusinessException {
 
     /**
      * This class will be initialized either by action status and params or by ResponseFormat
diff --git a/catalog-be/src/main/java/org/openecomp/sdc/be/servlets/ComponentInstanceCapabilityServlet.java b/catalog-be/src/main/java/org/openecomp/sdc/be/servlets/ComponentInstanceCapabilityServlet.java
new file mode 100644 (file)
index 0000000..f83f83d
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.openecomp.sdc.be.servlets;
+
+import com.jcabi.aspects.Loggable;
+import fj.data.Either;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.ArraySchema;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.servers.Server;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.openecomp.sdc.be.components.impl.ComponentInstanceBusinessLogic;
+import org.openecomp.sdc.be.components.impl.ResponseFormatManager;
+import org.openecomp.sdc.be.components.impl.aaf.AafPermission;
+import org.openecomp.sdc.be.components.impl.aaf.PermissionAllowed;
+import org.openecomp.sdc.be.config.BeEcompErrorManager;
+import org.openecomp.sdc.be.dao.api.ActionStatus;
+import org.openecomp.sdc.be.datatypes.enums.ComponentTypeEnum;
+import org.openecomp.sdc.be.exception.BusinessException;
+import org.openecomp.sdc.be.model.CapabilityDefinition;
+import org.openecomp.sdc.be.servlets.builder.ServletResponseBuilder;
+import org.openecomp.sdc.be.ui.mapper.CapabilityMapper;
+import org.openecomp.sdc.be.ui.model.ComponentInstanceCapabilityUpdateModel;
+import org.openecomp.sdc.common.api.Constants;
+import org.openecomp.sdc.common.log.elements.LoggerSupportability;
+import org.openecomp.sdc.common.log.enums.LoggerSupportabilityActions;
+import org.openecomp.sdc.common.log.enums.StatusCode;
+import org.openecomp.sdc.common.log.wrappers.Logger;
+import org.openecomp.sdc.exception.ResponseFormat;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestBody;
+
+/**
+ * Handles component instance capabilities operations
+ */
+@Loggable(prepend = true, value = Loggable.DEBUG, trim = false)
+@Path("/v1/catalog")
+@Tag(name = "SDCE-2 APIs")
+@Server(url = "/sdc2/rest")
+@Controller
+public class ComponentInstanceCapabilityServlet {
+
+    private static final Logger LOGGER = Logger.getLogger(ComponentInstanceCapabilityServlet.class);
+    private static final LoggerSupportability LOGGER_SUPPORTABILITY = LoggerSupportability.getLogger(ComponentInstanceCapabilityServlet.class);
+
+    private final ResponseFormatManager responseFormatManager;
+    private final ComponentInstanceBusinessLogic componentInstanceBusinessLogic;
+    private final CapabilityMapper capabilityMapper;
+    private final ServletResponseBuilder servletResponseBuilder;
+
+    public ComponentInstanceCapabilityServlet(final ComponentInstanceBusinessLogic componentInstanceBusinessLogic,
+                                              final CapabilityMapper capabilityMapper, final ServletResponseBuilder servletResponseBuilder) {
+        this.capabilityMapper = capabilityMapper;
+        this.servletResponseBuilder = servletResponseBuilder;
+        this.responseFormatManager = ResponseFormatManager.getInstance();
+        this.componentInstanceBusinessLogic = componentInstanceBusinessLogic;
+    }
+
+    @PUT
+    @Path("/{containerComponentType}/{containerComponentId}/componentInstances/{componentInstanceUniqueId}/capability/")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Operation(description = "Update Component Instance Capability", method = "PUT", summary = "Returns updated Component Instance Capability",
+        responses = {
+            @ApiResponse(content = @Content(array = @ArraySchema(schema = @Schema(implementation = Response.class)))),
+            @ApiResponse(responseCode = "200", description = "Resource instance capability successfully updated"),
+            @ApiResponse(responseCode = "403", description = "Restricted operation"),
+            @ApiResponse(responseCode = "400", description = "Invalid content / Missing content"),
+            @ApiResponse(responseCode = "404", description = "Component/Component Instance/Capability not found")})
+    @PermissionAllowed(AafPermission.PermNames.INTERNAL_ALL_VALUE)
+    public Response updateInstanceRequirement(@PathParam("containerComponentType") final String containerComponentType,
+                                              @PathParam("containerComponentId") final String containerComponentId,
+                                              @PathParam("componentInstanceUniqueId") final String componentInstanceUniqueId,
+                                              @Parameter(description = "Component instance capability to update", required = true)
+                                              @Valid @RequestBody @NotNull final ComponentInstanceCapabilityUpdateModel capabilityUpdateModel,
+                                              @Context final HttpServletRequest request,
+                                              @HeaderParam(value = Constants.USER_ID_HEADER) final String userId) {
+        if (LOGGER.isDebugEnabled()) {
+            final var url = request.getMethod() + " " + request.getRequestURI();
+            LOGGER.debug("Start handle request of {}", url);
+        }
+        try {
+            var componentTypeEnum = ComponentTypeEnum.findByParamName(containerComponentType);
+            if (componentTypeEnum == null) {
+                LOGGER.debug("Unsupported component type {}", containerComponentType);
+                return servletResponseBuilder
+                    .buildErrorResponse(responseFormatManager.getResponseFormat(ActionStatus.UNSUPPORTED_ERROR, containerComponentType));
+            }
+            final var capabilityDefinition = capabilityMapper.mapToCapabilityDefinition(capabilityUpdateModel);
+            LOGGER_SUPPORTABILITY.log(LoggerSupportabilityActions.UPDATE_INSTANCE_CAPABILITY, StatusCode.STARTED,
+                "Starting to update capability '{}' in component instance '{}' by '{}'",
+                capabilityDefinition.getName(), componentInstanceUniqueId, userId);
+            final Either<CapabilityDefinition, ResponseFormat> response = componentInstanceBusinessLogic
+                .updateInstanceCapability(componentTypeEnum, containerComponentId, componentInstanceUniqueId, capabilityDefinition, userId);
+            if (response.isRight()) {
+                return servletResponseBuilder.buildErrorResponse(response.right().value());
+            }
+            return servletResponseBuilder.buildOkResponse(responseFormatManager.getResponseFormat(ActionStatus.OK), response.left().value());
+        } catch (final BusinessException e) {
+            //leave to the handlers deal with it
+            throw e;
+        } catch (final Exception e) {
+            var errorMsg = String
+                .format("Unexpected error while updating component '%s' of type '%s' instance '%s'. Payload '%s'",
+                    containerComponentId, containerComponentType, componentInstanceUniqueId, capabilityUpdateModel);
+            BeEcompErrorManager.getInstance().logBeRestApiGeneralError(errorMsg);
+            LOGGER.debug(errorMsg, e);
+            return servletResponseBuilder.buildErrorResponse(responseFormatManager.getResponseFormat(ActionStatus.GENERAL_ERROR));
+        }
+    }
+
+}
index 7816d3d..d44c26e 100644 (file)
@@ -1046,7 +1046,7 @@ public class ComponentInstanceServlet extends AbstractValidationsServlet {
         try {
             log.debug(START_HANDLE_REQUEST_OF, url);
             ComponentTypeEnum componentTypeEnum = ComponentTypeEnum.findByParamName(containerComponentType);
-            if (componentInstanceBusinessLogic == null) {
+            if (componentTypeEnum == null) {
                 log.debug(UNSUPPORTED_COMPONENT_TYPE, containerComponentType);
                 return buildErrorResponse(getComponentsUtils().getResponseFormat(ActionStatus.UNSUPPORTED_ERROR, containerComponentType));
             }
diff --git a/catalog-be/src/main/java/org/openecomp/sdc/be/servlets/builder/ServletResponseBuilder.java b/catalog-be/src/main/java/org/openecomp/sdc/be/servlets/builder/ServletResponseBuilder.java
new file mode 100644 (file)
index 0000000..a36ccf5
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.openecomp.sdc.be.servlets.builder;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import java.util.Map;
+import java.util.Map.Entry;
+import javax.ws.rs.core.Response;
+import org.apache.commons.collections.MapUtils;
+import org.openecomp.sdc.common.log.wrappers.Logger;
+import org.openecomp.sdc.exception.ResponseFormat;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ServletResponseBuilder {
+
+    private final Gson gson;
+
+    public ServletResponseBuilder() {
+        gson = new GsonBuilder().setPrettyPrinting().create();
+    }
+
+    private static final Logger log = Logger.getLogger(ServletResponseBuilder.class);
+
+    public Response buildErrorResponse(final ResponseFormat requestErrorWrapper) {
+        return Response.status(requestErrorWrapper.getStatus()).entity(gson.toJson(requestErrorWrapper.getRequestError())).build();
+    }
+
+    public Response buildOkResponse(final ResponseFormat errorResponseWrapper, final Object entity) {
+        return buildOkResponse(errorResponseWrapper, entity, null);
+    }
+
+    public Response buildOkResponse(final ResponseFormat errorResponseWrapper, final Object entity,
+                                    final Map<String, String> additionalHeaders) {
+        final int status = errorResponseWrapper.getStatus();
+        var responseBuilder = Response.status(status);
+        if (entity != null) {
+            if (log.isTraceEnabled()) {
+                log.trace("returned entity is {}", entity.toString());
+            }
+            responseBuilder = responseBuilder.entity(entity);
+        }
+        if (MapUtils.isNotEmpty(additionalHeaders)) {
+            for (final Entry<String, String> additionalHeader : additionalHeaders.entrySet()) {
+                final String headerName = additionalHeader.getKey();
+                final String headerValue = additionalHeader.getValue();
+                log.trace("Adding header {} with value {} to the response", headerName, headerValue);
+                responseBuilder = responseBuilder.header(headerName, headerValue);
+            }
+        }
+        return responseBuilder.build();
+    }
+
+}
diff --git a/catalog-be/src/main/java/org/openecomp/sdc/be/servlets/exception/OperationExceptionMapper.java b/catalog-be/src/main/java/org/openecomp/sdc/be/servlets/exception/OperationExceptionMapper.java
new file mode 100644 (file)
index 0000000..7c25f8a
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.openecomp.sdc.be.servlets.exception;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+import org.openecomp.sdc.be.components.impl.ResponseFormatManager;
+import org.openecomp.sdc.be.model.jsonjanusgraph.operations.exception.OperationException;
+import org.openecomp.sdc.be.servlets.builder.ServletResponseBuilder;
+import org.openecomp.sdc.common.log.wrappers.Logger;
+import org.springframework.stereotype.Component;
+
+@Component
+@Provider
+public class OperationExceptionMapper implements ExceptionMapper<OperationException> {
+
+    private final ServletResponseBuilder servletResponseBuilder;
+    private final ResponseFormatManager responseFormatManager;
+
+    private static final Logger log = Logger.getLogger(OperationExceptionMapper.class);
+
+    public OperationExceptionMapper(final ServletResponseBuilder servletResponseBuilder) {
+        this.servletResponseBuilder = servletResponseBuilder;
+        this.responseFormatManager = ResponseFormatManager.getInstance();
+    }
+
+    @Override
+    public Response toResponse(final OperationException exception) {
+        log.debug("Handling OperationException response", exception);
+        return servletResponseBuilder.buildErrorResponse(responseFormatManager.getResponseFormat(exception.getActionStatus(), exception.getParams()));
+    }
+}
index ac4f8bf..88f3666 100644 (file)
@@ -323,9 +323,11 @@ public class CapabilityRequirementConverter {
         Map<String, String[]> toscaCapabilities = new HashMap<>();
         Either<Map<String, String[]>, ToscaError> result = null;
         for (Map.Entry<String, List<CapabilityDefinition>> entry : capabilities.entrySet()) {
-            Optional<CapabilityDefinition> failedToAddRequirement = entry.getValue().stream().filter(
-                c -> !addEntry(componentsCache, toscaCapabilities, component, new SubstitutionEntry(c.getName(), c.getParentName(), ""),
-                    c.getPreviousName(), c.getOwnerId(), c.getPath())).findAny();
+            Optional<CapabilityDefinition> failedToAddRequirement = entry.getValue().stream()
+                .filter(CapabilityDataDefinition::isExternal)
+                .filter( c -> !addEntry(componentsCache, toscaCapabilities, component, new SubstitutionEntry(c.getName(), c.getParentName(), "")
+                    , c.getPreviousName(), c.getOwnerId(), c.getPath()))
+                .findAny();
             if (failedToAddRequirement.isPresent()) {
                 logger.debug("Failed to convert capability {} for substitution mappings section of a tosca template of the component {}. ",
                     failedToAddRequirement.get().getName(), component.getName());
index 570cabe..0c78b1f 100644 (file)
@@ -65,7 +65,8 @@ import org.springframework.core.annotation.Order;
     "org.openecomp.sdc.be.servlets",
     "org.openecomp.sdc.be.filters",
     "org.openecomp.sdc.be.plugins",
-    "org.openecomp.sdc.be.togglz"})
+    "org.openecomp.sdc.be.togglz",
+    "org.openecomp.sdc.be.ui.mapper"})
 // @formatter:on
 public class CatalogBESpringConfig {
 
index 07fff19..5c8b6ce 100644 (file)
@@ -23,10 +23,12 @@ package org.openecomp.sdc.be.components.impl;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.DynamicTest.dynamicTest;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anySet;
@@ -79,12 +81,12 @@ import org.openecomp.sdc.be.datatypes.elements.GetInputValueDataDefinition;
 import org.openecomp.sdc.be.datatypes.elements.GetPolicyValueDataDefinition;
 import org.openecomp.sdc.be.datatypes.elements.ListDataDefinition;
 import org.openecomp.sdc.be.datatypes.elements.RequirementDataDefinition;
-import org.openecomp.sdc.be.datatypes.elements.SchemaDefinition;
 import org.openecomp.sdc.be.datatypes.enums.ComponentTypeEnum;
 import org.openecomp.sdc.be.datatypes.enums.JsonPresentationFields;
 import org.openecomp.sdc.be.datatypes.enums.NodeTypeEnum;
 import org.openecomp.sdc.be.datatypes.enums.OriginTypeEnum;
 import org.openecomp.sdc.be.datatypes.enums.ResourceTypeEnum;
+import org.openecomp.sdc.be.exception.BusinessException;
 import org.openecomp.sdc.be.impl.ComponentsUtils;
 import org.openecomp.sdc.be.model.ArtifactDefinition;
 import org.openecomp.sdc.be.model.CapabilityDefinition;
@@ -111,6 +113,7 @@ import org.openecomp.sdc.be.model.cache.ApplicationDataTypeCache;
 import org.openecomp.sdc.be.model.jsonjanusgraph.config.ContainerInstanceTypesData;
 import org.openecomp.sdc.be.model.jsonjanusgraph.operations.ForwardingPathOperation;
 import org.openecomp.sdc.be.model.jsonjanusgraph.operations.ToscaOperationFacade;
+import org.openecomp.sdc.be.model.jsonjanusgraph.operations.exception.OperationException;
 import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus;
 import org.openecomp.sdc.be.model.operations.impl.GraphLockOperation;
 import org.openecomp.sdc.be.model.operations.impl.PropertyOperation;
@@ -201,7 +204,7 @@ class ComponentInstanceBusinessLogicTest {
 
     @BeforeEach
     void init() {
-        MockitoAnnotations.initMocks(this);
+        MockitoAnnotations.openMocks(this);
         stubMethods();
         createComponents();
     }
@@ -1950,6 +1953,216 @@ class ComponentInstanceBusinessLogicTest {
 
     }
 
+    @Test
+    void updateInstanceCapabilitySuccessTest() {
+        var containerComponentId = "containerComponentId";
+        var componentInstanceUniqueId = "componentInstanceUniqueId";
+        var capabilityDefinition = new CapabilityDefinition();
+        capabilityDefinition.setUniqueId("uniqueId");
+
+        final Component component = new Service();
+        component.setUniqueId(containerComponentId);
+        component.setLastUpdaterUserId(USER_ID);
+        component.setLifecycleState(LifecycleStateEnum.NOT_CERTIFIED_CHECKOUT);
+
+        var componentInstance = new ComponentInstance();
+        componentInstance.setUniqueId(componentInstanceUniqueId);
+        component.setComponentInstances(Collections.singletonList(componentInstance));
+
+        when(toscaOperationFacade.getToscaFullElement(containerComponentId))
+            .thenReturn(Either.left(component));
+        when(toscaOperationFacade.updateComponentInstanceCapability(containerComponentId, componentInstanceUniqueId, capabilityDefinition))
+            .thenReturn(capabilityDefinition);
+        when(toscaOperationFacade.updateComponentInstanceMetadataOfTopologyTemplate(component))
+            .thenReturn(Either.left(component));
+        when(graphLockOperation.lockComponent(containerComponentId, NodeTypeEnum.Service))
+            .thenReturn(StorageOperationStatus.OK);
+
+        final Either<CapabilityDefinition, ResponseFormat> resultEither = componentInstanceBusinessLogic
+            .updateInstanceCapability(ComponentTypeEnum.SERVICE, containerComponentId, componentInstanceUniqueId, capabilityDefinition, USER_ID);
+        assertTrue(resultEither.isLeft());
+        final CapabilityDefinition actualCapabilityDefinition = resultEither.left().value();
+        assertNotEquals(capabilityDefinition, actualCapabilityDefinition);
+        assertEquals(capabilityDefinition.getUniqueId(), actualCapabilityDefinition.getUniqueId());
+    }
+
+    @Test
+    void updateInstanceCapabilityNoContainerComponentTypeTest() {
+        var responseFormat = new ResponseFormat();
+        when(componentsUtils.getResponseFormat(ActionStatus.NOT_ALLOWED)).thenReturn(responseFormat);
+        final Either<CapabilityDefinition, ResponseFormat> resultEither = componentInstanceBusinessLogic
+            .updateInstanceCapability(null, "containerComponentId", "componentInstanceUniqueId", new CapabilityDefinition(), USER_ID);
+        assertTrue(resultEither.isRight(), "Either return should be right");
+        final ResponseFormat actualResponseFormat = resultEither.right().value();
+        assertEquals(responseFormat, actualResponseFormat);
+    }
+
+    @Test
+    void updateInstanceCapabilityContainerComponentNotFoundTest() {
+        var containerComponentId = "containerComponentId";
+        when(toscaOperationFacade.getToscaFullElement(containerComponentId)).thenReturn(Either.right(null));
+        var responseFormat = new ResponseFormat();
+        when(componentsUtils.getResponseFormat(ActionStatus.COMPONENT_NOT_FOUND, containerComponentId)).thenReturn(responseFormat);
+        final Either<CapabilityDefinition, ResponseFormat> resultEither = componentInstanceBusinessLogic
+            .updateInstanceCapability(ComponentTypeEnum.SERVICE, "containerComponentId", "componentInstanceUniqueId", new CapabilityDefinition(), USER_ID);
+        assertTrue(resultEither.isRight(), "Either return should be right");
+        final ResponseFormat actualResponseFormat = resultEither.right().value();
+        assertEquals(responseFormat, actualResponseFormat);
+    }
+
+    @Test
+    void updateInstanceCapabilityCannotWorkOnComponentTest() {
+        var containerComponentId = "containerComponentId";
+        var componentInstanceUniqueId = "componentInstanceUniqueId";
+
+        final Component component = new Service();
+        component.setUniqueId(containerComponentId);
+        component.setLastUpdaterUserId("anotherUse");
+        component.setLifecycleState(LifecycleStateEnum.NOT_CERTIFIED_CHECKOUT);
+
+        var expectedResponseFormat = new ResponseFormat();
+
+        when(toscaOperationFacade.getToscaFullElement(containerComponentId))
+            .thenReturn(Either.left(component));
+        when(componentsUtils.getResponseFormat(ActionStatus.RESTRICTED_OPERATION))
+            .thenReturn(expectedResponseFormat);
+
+        final Either<CapabilityDefinition, ResponseFormat> resultEither = componentInstanceBusinessLogic
+            .updateInstanceCapability(ComponentTypeEnum.SERVICE, containerComponentId, componentInstanceUniqueId, new CapabilityDefinition(), USER_ID);
+        assertTrue(resultEither.isRight(), "Either return should be right");
+        final ResponseFormat actualResponseFormat = resultEither.right().value();
+        assertEquals(expectedResponseFormat, actualResponseFormat);
+    }
+
+    @Test
+    void updateInstanceCapabilityResourceInstanceNotFoundTest() {
+        var containerComponentId = "containerComponentId";
+        var componentInstanceUniqueId = "componentInstanceUniqueId";
+
+        final Component component = new Service();
+        component.setUniqueId(containerComponentId);
+        component.setLastUpdaterUserId(USER_ID);
+        component.setLifecycleState(LifecycleStateEnum.NOT_CERTIFIED_CHECKOUT);
+
+        var expectedResponseFormat = new ResponseFormat();
+
+        when(toscaOperationFacade.getToscaFullElement(containerComponentId))
+            .thenReturn(Either.left(component));
+        when(componentsUtils.getResponseFormat(ActionStatus.RESOURCE_INSTANCE_NOT_FOUND_ON_SERVICE, componentInstanceUniqueId, containerComponentId))
+            .thenReturn(expectedResponseFormat);
+
+        final Either<CapabilityDefinition, ResponseFormat> resultEither = componentInstanceBusinessLogic
+            .updateInstanceCapability(ComponentTypeEnum.SERVICE, containerComponentId, componentInstanceUniqueId, new CapabilityDefinition(), USER_ID);
+        assertTrue(resultEither.isRight(), "Either return should be right");
+        final ResponseFormat actualResponseFormat = resultEither.right().value();
+        assertEquals(expectedResponseFormat, actualResponseFormat);
+    }
+
+    @Test
+    void updateInstanceCapabilityUpdateMetadataFailTest() {
+        var containerComponentId = "containerComponentId";
+        var componentInstanceUniqueId = "componentInstanceUniqueId";
+        var capabilityDefinition = new CapabilityDefinition();
+        capabilityDefinition.setUniqueId("uniqueId");
+
+        final Component component = new Service();
+        component.setUniqueId(containerComponentId);
+        component.setLastUpdaterUserId(USER_ID);
+        component.setLifecycleState(LifecycleStateEnum.NOT_CERTIFIED_CHECKOUT);
+
+        var componentInstance = new ComponentInstance();
+        componentInstance.setUniqueId(componentInstanceUniqueId);
+        component.setComponentInstances(Collections.singletonList(componentInstance));
+
+        var expectedResponseFormat = new ResponseFormat();
+
+        when(toscaOperationFacade.getToscaFullElement(containerComponentId))
+            .thenReturn(Either.left(component));
+        when(graphLockOperation.lockComponent(containerComponentId, NodeTypeEnum.Service))
+            .thenReturn(StorageOperationStatus.OK);
+        when(toscaOperationFacade.updateComponentInstanceCapability(containerComponentId, componentInstanceUniqueId, capabilityDefinition))
+            .thenReturn(capabilityDefinition);
+        when(toscaOperationFacade.updateComponentInstanceMetadataOfTopologyTemplate(component))
+            .thenReturn(Either.right(StorageOperationStatus.GENERAL_ERROR));
+        when(componentsUtils.convertFromStorageResponse(StorageOperationStatus.GENERAL_ERROR, ComponentTypeEnum.SERVICE))
+            .thenReturn(ActionStatus.GENERAL_ERROR);
+        when(componentsUtils.getResponseFormat(ActionStatus.GENERAL_ERROR))
+            .thenReturn(expectedResponseFormat);
+
+        final Either<CapabilityDefinition, ResponseFormat> resultEither = componentInstanceBusinessLogic
+            .updateInstanceCapability(ComponentTypeEnum.SERVICE, containerComponentId, componentInstanceUniqueId, capabilityDefinition, USER_ID);
+        assertTrue(resultEither.isRight(), "Either return should be right");
+        final ResponseFormat actualResponseFormat = resultEither.right().value();
+        assertEquals(expectedResponseFormat, actualResponseFormat);
+    }
+
+    @Test
+    void updateInstanceCapabilityBusinessExceptionHandlingTest() {
+        var containerComponentId = "containerComponentId";
+        var componentInstanceUniqueId = "componentInstanceUniqueId";
+        var capabilityDefinition = new CapabilityDefinition();
+        capabilityDefinition.setUniqueId("uniqueId");
+
+        final Component component = new Service();
+        component.setUniqueId(containerComponentId);
+        component.setLastUpdaterUserId(USER_ID);
+        component.setLifecycleState(LifecycleStateEnum.NOT_CERTIFIED_CHECKOUT);
+
+        var componentInstance = new ComponentInstance();
+        componentInstance.setUniqueId(componentInstanceUniqueId);
+        component.setComponentInstances(Collections.singletonList(componentInstance));
+
+
+        when(toscaOperationFacade.getToscaFullElement(containerComponentId))
+            .thenReturn(Either.left(component));
+        when(graphLockOperation.lockComponent(containerComponentId, NodeTypeEnum.Service))
+            .thenReturn(StorageOperationStatus.OK);
+        when(toscaOperationFacade.updateComponentInstanceCapability(containerComponentId, componentInstanceUniqueId, capabilityDefinition))
+            .thenThrow(new OperationException(ActionStatus.GENERAL_ERROR));
+
+        final BusinessException businessException = assertThrows(BusinessException.class, () -> {
+            componentInstanceBusinessLogic
+                .updateInstanceCapability(ComponentTypeEnum.SERVICE, containerComponentId, componentInstanceUniqueId, capabilityDefinition, USER_ID);
+        });
+        assertTrue(businessException instanceof OperationException);
+        assertEquals(ActionStatus.GENERAL_ERROR, ((OperationException) businessException).getActionStatus());
+    }
+
+    @Test
+    void updateInstanceCapabilityUnknownExceptionHandlingTest() {
+        var containerComponentId = "containerComponentId";
+        var componentInstanceUniqueId = "componentInstanceUniqueId";
+        var capabilityDefinition = new CapabilityDefinition();
+        capabilityDefinition.setUniqueId("uniqueId");
+
+        final Component component = new Service();
+        component.setUniqueId(containerComponentId);
+        component.setLastUpdaterUserId(USER_ID);
+        component.setLifecycleState(LifecycleStateEnum.NOT_CERTIFIED_CHECKOUT);
+
+        var componentInstance = new ComponentInstance();
+        componentInstance.setUniqueId(componentInstanceUniqueId);
+        component.setComponentInstances(Collections.singletonList(componentInstance));
+
+        var expectedResponseFormat = new ResponseFormat();
+
+        when(toscaOperationFacade.getToscaFullElement(containerComponentId))
+            .thenReturn(Either.left(component));
+        when(graphLockOperation.lockComponent(containerComponentId, NodeTypeEnum.Service))
+            .thenReturn(StorageOperationStatus.OK);
+        when(toscaOperationFacade.updateComponentInstanceCapability(containerComponentId, componentInstanceUniqueId, capabilityDefinition))
+            .thenThrow(new RuntimeException());
+        when(componentsUtils.getResponseFormat(ActionStatus.GENERAL_ERROR))
+            .thenReturn(expectedResponseFormat);
+
+        final Exception exception = assertThrows(BusinessException.class, () ->
+            componentInstanceBusinessLogic
+                .updateInstanceCapability(ComponentTypeEnum.SERVICE, containerComponentId, componentInstanceUniqueId, capabilityDefinition, USER_ID));
+        assertTrue(exception instanceof ByResponseFormatComponentException);
+        final ByResponseFormatComponentException actualException = (ByResponseFormatComponentException) exception;
+        assertEquals(expectedResponseFormat, actualException.getResponseFormat());
+    }
+
     private ComponentInstance createServiceSubstitutionComponentInstance() {
         final ComponentInstance instanceToBeCreated = new ComponentInstance();
         instanceToBeCreated.setName(COMPONENT_INSTANCE_NAME);
diff --git a/catalog-be/src/test/java/org/openecomp/sdc/be/servlets/ComponentInstanceCapabilityServletTest.java b/catalog-be/src/test/java/org/openecomp/sdc/be/servlets/ComponentInstanceCapabilityServletTest.java
new file mode 100644 (file)
index 0000000..8155f22
--- /dev/null
@@ -0,0 +1,240 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.openecomp.sdc.be.servlets;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import fj.data.Either;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.openecomp.sdc.be.components.impl.ComponentInstanceBusinessLogic;
+import org.openecomp.sdc.be.config.ConfigurationManager;
+import org.openecomp.sdc.be.config.ErrorInfo;
+import org.openecomp.sdc.be.dao.api.ActionStatus;
+import org.openecomp.sdc.be.datatypes.enums.ComponentTypeEnum;
+import org.openecomp.sdc.be.model.CapabilityDefinition;
+import org.openecomp.sdc.be.model.jsonjanusgraph.operations.exception.OperationException;
+import org.openecomp.sdc.be.servlets.builder.ServletResponseBuilder;
+import org.openecomp.sdc.be.servlets.exception.OperationExceptionMapper;
+import org.openecomp.sdc.be.ui.mapper.CapabilityMapper;
+import org.openecomp.sdc.be.ui.model.ComponentInstanceCapabilityUpdateModel;
+import org.openecomp.sdc.common.api.ConfigurationSource;
+import org.openecomp.sdc.common.api.Constants;
+import org.openecomp.sdc.common.impl.ExternalConfiguration;
+import org.openecomp.sdc.common.impl.FSConfigurationSource;
+import org.openecomp.sdc.exception.ResponseFormat;
+import org.openecomp.sdc.exception.ServiceException;
+
+class ComponentInstanceCapabilityServletTest extends JerseySpringBaseTest {
+
+    private static final String UPDATE_INSTANCE_REQUIREMENT_PATH_FORMAT = "/v1/catalog/%s/%s/componentInstances/%s/capability";
+    private static ConfigurationManager configurationManager;
+
+    private final String componentId = "componentId";
+    private final String componentInstanceId = "componentInstanceId";
+    private final String userId = "userId";
+
+    private CapabilityMapper capabilityMapper;
+    private ComponentInstanceBusinessLogic componentInstanceBusinessLogicMock;
+
+    @Override
+    protected ResourceConfig configure() {
+        componentInstanceBusinessLogicMock = mock(ComponentInstanceBusinessLogic.class);
+        capabilityMapper = new CapabilityMapper();
+        var servletResponseBuilder = new ServletResponseBuilder();
+        var componentInstanceCapabilityServlet =
+            new ComponentInstanceCapabilityServlet(componentInstanceBusinessLogicMock, capabilityMapper, servletResponseBuilder);
+        return super.configure().register(componentInstanceCapabilityServlet)
+            .register(new OperationExceptionMapper(servletResponseBuilder));
+    }
+
+    @BeforeAll
+    static void beforeAll() {
+        setupConfiguration();
+    }
+
+    private static void setupConfiguration() {
+        final ConfigurationSource configurationSource =
+            new FSConfigurationSource(ExternalConfiguration.getChangeListener(), "src/test/resources/config/catalog-be");
+        configurationManager = new ConfigurationManager(configurationSource);
+    }
+
+    //workaround for JerseyTest + Junit5
+    @BeforeEach
+    void beforeEach() throws Exception {
+        super.setUp();
+    }
+
+    //workaround for JerseyTest + Junit5
+    @AfterEach
+    void afterEach() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    void updateInstanceRequirementSuccessTest() throws JsonProcessingException {
+        final var updateModel = createDefaultUpdateMode();
+        var expectedCapabilityDefinition = capabilityMapper.mapToCapabilityDefinition(updateModel);
+
+        when(componentInstanceBusinessLogicMock
+            .updateInstanceCapability(eq(ComponentTypeEnum.SERVICE), eq(componentId), eq(componentInstanceId), any(CapabilityDefinition.class), eq(userId)))
+            .thenReturn(Either.left(expectedCapabilityDefinition));
+        final var url =
+            String.format(UPDATE_INSTANCE_REQUIREMENT_PATH_FORMAT, ComponentTypeEnum.SERVICE_PARAM_NAME, componentId, componentInstanceId);
+
+        final Response response = target()
+            .path(url)
+            .request(MediaType.APPLICATION_JSON)
+            .header(Constants.USER_ID_HEADER, userId)
+            .put(Entity.entity(parseToJson(updateModel), MediaType.APPLICATION_JSON));
+        var actualCapabilityDefinition = response.readEntity(CapabilityDefinition.class);
+        assertEquals(200, response.getStatus(), "The update status should be as expected");
+        assertEquals(MediaType.valueOf(MediaType.APPLICATION_JSON), response.getMediaType(), "The content type should be application/json");
+        assertCapabilityDefinition(actualCapabilityDefinition, expectedCapabilityDefinition);
+    }
+
+    @Test
+    void updateInstanceRequirementFailTest() throws JsonProcessingException {
+        final var expectedResponseFormat = new ResponseFormat(404);
+        final var requestErrorWrapper = expectedResponseFormat.new RequestErrorWrapper();
+        final var serviceException = new ServiceException("anErrorCode", "anErrorText", new String[2]);
+        requestErrorWrapper.setServiceException(serviceException);
+        expectedResponseFormat.setRequestError(requestErrorWrapper);
+
+        when(componentInstanceBusinessLogicMock
+            .updateInstanceCapability(eq(ComponentTypeEnum.SERVICE), eq(componentId), eq(componentInstanceId), any(CapabilityDefinition.class), eq(userId)))
+            .thenReturn(Either.right(expectedResponseFormat));
+
+        final var url =
+            String.format(UPDATE_INSTANCE_REQUIREMENT_PATH_FORMAT, ComponentTypeEnum.SERVICE_PARAM_NAME, componentId, componentInstanceId);
+        final Response response = target()
+            .path(url)
+            .request(MediaType.APPLICATION_JSON)
+            .header(Constants.USER_ID_HEADER, userId)
+            .put(Entity.entity(parseToJson(createDefaultUpdateMode()), MediaType.APPLICATION_JSON));
+        var actualResponseFormat = response.readEntity(ResponseFormat.class);
+
+        assertEquals(expectedResponseFormat.getStatus(), response.getStatus(), "The update status should be as expected");
+        assertNotNull(actualResponseFormat.getRequestError());
+        assertNotNull(actualResponseFormat.getRequestError().getRequestError());
+        assertEquals(expectedResponseFormat.getMessageId(), actualResponseFormat.getMessageId());
+        assertEquals(expectedResponseFormat.getVariables().length, actualResponseFormat.getVariables().length);
+    }
+
+    @Test
+    void updateInstanceRequirementKnownErrorTest() throws JsonProcessingException {
+        when(componentInstanceBusinessLogicMock
+            .updateInstanceCapability(eq(ComponentTypeEnum.SERVICE), eq(componentId), eq(componentInstanceId), any(CapabilityDefinition.class), eq(userId)))
+            .thenThrow(new OperationException(ActionStatus.COMPONENT_NOT_FOUND, componentId));
+        final var url =
+            String.format(UPDATE_INSTANCE_REQUIREMENT_PATH_FORMAT, ComponentTypeEnum.SERVICE_PARAM_NAME, componentId, componentInstanceId);
+        final Response response = target()
+            .path(url)
+            .request(MediaType.APPLICATION_JSON)
+            .header(Constants.USER_ID_HEADER, userId)
+            .put(Entity.entity(parseToJson(createDefaultUpdateMode()), MediaType.APPLICATION_JSON));
+        var responseFormat = response.readEntity(ResponseFormat.class);
+
+        final ErrorInfo errorInfo = configurationManager.getErrorConfiguration().getErrorInfo(ActionStatus.COMPONENT_NOT_FOUND.name());
+        assertNotNull(errorInfo);
+        assertEquals(errorInfo.getCode(), response.getStatus(), "The update status should be as expected");
+        assertEquals(errorInfo.getMessageId(), responseFormat.getMessageId());
+        assertEquals(errorInfo.getMessage(), responseFormat.getText());
+    }
+
+    @Test
+    void updateInstanceRequirementUnknownErrorTest() throws JsonProcessingException {
+        when(componentInstanceBusinessLogicMock
+            .updateInstanceCapability(eq(ComponentTypeEnum.SERVICE), eq(componentId), eq(componentInstanceId), any(CapabilityDefinition.class), eq(userId)))
+            .thenThrow(new RuntimeException());
+        final var url =
+            String.format(UPDATE_INSTANCE_REQUIREMENT_PATH_FORMAT, ComponentTypeEnum.SERVICE_PARAM_NAME, componentId, componentInstanceId);
+        final Response response = target()
+            .path(url)
+            .request(MediaType.APPLICATION_JSON)
+            .header(Constants.USER_ID_HEADER, userId)
+            .put(Entity.entity(parseToJson(createDefaultUpdateMode()), MediaType.APPLICATION_JSON));
+        var responseFormat = response.readEntity(ResponseFormat.class);
+
+        final ErrorInfo errorInfo = configurationManager.getErrorConfiguration().getErrorInfo(ActionStatus.GENERAL_ERROR.name());
+        assertNotNull(errorInfo);
+        assertEquals(errorInfo.getCode(), response.getStatus(), "The update status should be as expected");
+        assertEquals(errorInfo.getMessageId(), responseFormat.getMessageId());
+        assertEquals(errorInfo.getMessage(), responseFormat.getText());
+    }
+
+    @Test
+    void updateInstanceRequirementIncorrectComponentTypeTest() throws JsonProcessingException {
+        final var url =
+            String.format(UPDATE_INSTANCE_REQUIREMENT_PATH_FORMAT, "wrongType", componentId, componentInstanceId);
+        final Response response = target()
+            .path(url)
+            .request(MediaType.APPLICATION_JSON)
+            .header(Constants.USER_ID_HEADER, userId)
+            .put(Entity.entity(parseToJson(createDefaultUpdateMode()), MediaType.APPLICATION_JSON));
+        var responseFormat = response.readEntity(ResponseFormat.class);
+
+        final ErrorInfo errorInfo = configurationManager.getErrorConfiguration().getErrorInfo(ActionStatus.UNSUPPORTED_ERROR.name());
+        assertNotNull(errorInfo);
+        assertEquals(errorInfo.getCode(), response.getStatus(), "The update status should be as expected");
+        assertEquals(errorInfo.getMessageId(), responseFormat.getMessageId());
+        assertEquals(errorInfo.getMessage(), responseFormat.getText());
+    }
+
+    private ComponentInstanceCapabilityUpdateModel createDefaultUpdateMode() {
+        var capabilityUpdateModel = new ComponentInstanceCapabilityUpdateModel();
+        capabilityUpdateModel.setName("name");
+        capabilityUpdateModel.setExternal(true);
+        capabilityUpdateModel.setOwnerId("ownerId");
+        capabilityUpdateModel.setType("type");
+        capabilityUpdateModel.setOwnerName("ownerName");
+        capabilityUpdateModel.setUniqueId("uniqueId");
+        return capabilityUpdateModel;
+    }
+
+    private void assertCapabilityDefinition(final CapabilityDefinition actualCapabilityDefinition,
+                                            final CapabilityDefinition expectedCapabilityDefinition) {
+        assertEquals(expectedCapabilityDefinition.getUniqueId(), actualCapabilityDefinition.getUniqueId());
+        assertEquals(expectedCapabilityDefinition.getName(), actualCapabilityDefinition.getName());
+        assertEquals(expectedCapabilityDefinition.getOwnerId(), actualCapabilityDefinition.getOwnerId());
+        assertEquals(expectedCapabilityDefinition.getOwnerName(), actualCapabilityDefinition.getOwnerName());
+        assertEquals(expectedCapabilityDefinition.getType(), actualCapabilityDefinition.getType());
+        assertEquals(expectedCapabilityDefinition.isExternal(), actualCapabilityDefinition.isExternal());
+    }
+
+    private <T> String parseToJson(final T object) throws JsonProcessingException {
+        return new ObjectMapper().writeValueAsString(object);
+    }
+
+}
+
index e755de1..7fda4ed 100644 (file)
@@ -77,6 +77,7 @@ public abstract class JerseySpringBaseTest extends JerseyTest {
             .register(jacksonJsonProvider);
     }
 
+    @Override
     protected ResourceConfig configure() {
         forceSet(TestProperties.CONTAINER_PORT, "0");
         return configure(BaseTestConfig.class);
index 49920c3..5f1b62d 100644 (file)
@@ -307,6 +307,7 @@ public class CapabilityRequirementConverterTest {
         CapabilityDefinition capabilityDefinition = new CapabilityDefinition();
         capabilityDefinition.setName(capabilityName);
         capabilityDefinition.setType("att.Node");
+        capabilityDefinition.setExternal(true);
         List<ComponentInstanceProperty> properties = new ArrayList<>();
         ComponentInstanceProperty prop = new ComponentInstanceProperty();
         prop.setName(PROPERTY_NAME);
index 50906d2..b363bac 100644 (file)
@@ -2266,3 +2266,31 @@ errors:
         message: "Error: Invalid content. Type name is not defined for policy %1",
         messageId: "SVC4802"
     }
+    #---------SVC4140------------------------------
+    # %1 - Component uid
+    COMPONENT_FIND_ERROR: {
+        code: 500,
+        message: "An unexpected error occurred while retrieving the component '%1'.",
+        messageId: "SVC4140"
+    }
+    #---------SVC4141------------------------------
+    # %1 - Component uid
+    COMPONENT_CAPABILITIES_FIND_ERROR: {
+        code: 500,
+        message: "An unexpected error occurred while retrieving the component '%1' capabilities.",
+        messageId: "SVC4141"
+    }
+    #---------SVC4142------------------------------
+    # %1 - Component uid or name
+    COMPONENT_NOT_FOUND: {
+        code: 404,
+        message: "Component '%1' was not found.",
+        messageId: "SVC4142"
+    }
+    #---------SVC4143------------------------------
+    # %1 - Capability name
+    COMPONENT_INSTANCE_CAPABILITY_UPDATE_ERROR: {
+        code: 500,
+        message: "An unexpected error occurred while updating the capability '%1'.",
+        messageId: "SVC4143"
+    }
\ No newline at end of file
index a8f94e8..619f882 100644 (file)
@@ -31,6 +31,10 @@ public enum ActionStatus {
     RESOURCE_NOT_FOUND, MISSING_DERIVED_FROM_TEMPLATE, PARENT_RESOURCE_NOT_FOUND, PARENT_RESOURCE_DOES_NOT_EXTEND, INVALID_DEFAULT_VALUE, INVALID_COMPLEX_DEFAULT_VALUE, MULTIPLE_PARENT_RESOURCE_FOUND, INVALID_RESOURCE_PAYLOAD, INVALID_TOSCA_FILE_EXTENSION, INVALID_YAML_FILE, INVALID_TOSCA_TEMPLATE, NOT_RESOURCE_TOSCA_TEMPLATE, NOT_SINGLE_RESOURCE, INVALID_RESOURCE_NAMESPACE, RESOURCE_ALREADY_EXISTS, INVALID_RESOURCE_CHECKSUM, RESOURCE_CANNOT_CONTAIN_RESOURCE_INSTANCES, NO_ASSETS_FOUND, GENERIC_TYPE_NOT_FOUND, INVALID_RESOURCE_TYPE, TOSCA_PARSE_ERROR, INVALID_PM_DICTIONARY_FILE,
     // Service related
     SERVICE_TYPE_EXCEEDS_LIMIT, INVALID_SERVICE_TYPE, SERVICE_ROLE_EXCEEDS_LIMIT, INVALID_SERVICE_ROLE, INVALID_INSTANTIATION_TYPE, NOT_SERVICE_TOSCA_TEMPLATE, NOT_SINGLE_SERVICE, INVALID_SERVICE_CHECKSUM, INVALID_SERVICE_PAYLOAD, INVALID_SERVICE_NAMESPACE, SERVICE_ALREADY_EXISTS, UNSUPPORTED_DISTRIBUTION_STATUS, INVALID_NAMING_POLICY, INVALID_ENVIRONMENT_CONTEXT, NAMING_POLICY_EXCEEDS_LIMIT, MISSING_ECOMP_GENERATED_NAMING, PROPERTY_EXCEEDS_LIMIT, INVALID_PROPERY,
+    // Component related
+    COMPONENT_FIND_ERROR,
+    COMPONENT_CAPABILITIES_FIND_ERROR,
+    COMPONENT_INSTANCE_CAPABILITY_UPDATE_ERROR,
     // Component name related
     COMPONENT_NAME_ALREADY_EXIST, COMPONENT_NAME_EXCEEDS_LIMIT, MISSING_COMPONENT_NAME, INVALID_COMPONENT_NAME,
     // Component description related
@@ -109,6 +113,7 @@ public enum ActionStatus {
     CONTAINER_CANNOT_CONTAIN_COMPONENT_IN_STATE, CONTAINER_CANNOT_CONTAIN_INSTANCE, MISSING_MANDATORY_PROPERTY, MANDATORY_PROPERTY_MISSING_VALUE,
     //Capability related
     CAPABILITY_NOT_FOUND, CAPABILITY_NAME_MANDATORY, CAPABILITY_TYPE_MANDATORY, CAPABILITY_NAME_ALREADY_IN_USE, MAX_OCCURRENCES_SHOULD_BE_GREATER_THAN_MIN_OCCURRENCES, CAPABILITY_DELETION_NOT_ALLOWED_USED_IN_COMPOSITION, CAPABILITY_UPDATE_NOT_ALLOWED_USED_IN_COMPOSITION, INVALID_CAPABILITY_NAME, FAILED_TO_CREATE_OR_UPDATE_CAPABILITY_PROPERTIES, CAPABILITY_PROPERTIES_NOT_FOUND, RELATIONSHIP_TYPE_ALREADY_EXIST, MISSING_RELATIONSHIP_TYPE, CAPABILITY_TYPE_CANNOT_BE_EMPTY,
+    COMPONENT_CAPABILITY_UPDATE_NOT_FOUND_ERROR,
     //Requirement related
     REQUIREMENT_NOT_FOUND, REQUIREMENT_NAME_MANDATORY, REQUIREMENT_CAPABILITY_MANDATORY, REQUIREMENT_NAME_ALREADY_IN_USE, REQUIREMENT_DELETION_NOT_ALLOWED_USED_IN_COMPOSITION, REQUIREMENT_UPDATE_NOT_ALLOWED_USED_IN_COMPOSITION, INVALID_REQUIREMENT_NAME,
     //Abstract template related
index f9e9a46..416c701 100644 (file)
@@ -45,6 +45,7 @@ import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.janusgraph.core.JanusGraphVertex;
 import org.openecomp.sdc.be.config.BeEcompErrorManager;
 import org.openecomp.sdc.be.config.ConfigurationManager;
+import org.openecomp.sdc.be.dao.api.ActionStatus;
 import org.openecomp.sdc.be.dao.janusgraph.JanusGraphOperationStatus;
 import org.openecomp.sdc.be.dao.jsongraph.GraphVertex;
 import org.openecomp.sdc.be.dao.jsongraph.types.EdgeLabelEnum;
@@ -105,6 +106,7 @@ import org.openecomp.sdc.be.model.jsonjanusgraph.datamodel.TopologyTemplate;
 import org.openecomp.sdc.be.model.jsonjanusgraph.datamodel.ToscaElement;
 import org.openecomp.sdc.be.model.jsonjanusgraph.datamodel.ToscaElementTypeEnum;
 import org.openecomp.sdc.be.model.jsonjanusgraph.enums.JsonConstantKeysEnum;
+import org.openecomp.sdc.be.model.jsonjanusgraph.operations.exception.OperationException;
 import org.openecomp.sdc.be.model.jsonjanusgraph.utils.ModelConverter;
 import org.openecomp.sdc.be.model.operations.StorageException;
 import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus;
@@ -115,7 +117,9 @@ import org.openecomp.sdc.common.api.ArtifactGroupTypeEnum;
 import org.openecomp.sdc.common.api.ArtifactTypeEnum;
 import org.openecomp.sdc.common.jsongraph.util.CommonUtility;
 import org.openecomp.sdc.common.jsongraph.util.CommonUtility.LogLevelEnum;
+import org.openecomp.sdc.common.log.elements.ErrorLogOptionalData;
 import org.openecomp.sdc.common.log.elements.LoggerSupportability;
+import org.openecomp.sdc.common.log.enums.EcompLoggerErrorCode;
 import org.openecomp.sdc.common.log.enums.LogLevel;
 import org.openecomp.sdc.common.log.enums.LoggerSupportabilityActions;
 import org.openecomp.sdc.common.log.enums.StatusCode;
@@ -957,11 +961,13 @@ public class NodeTemplateOperation extends BaseOperation {
         MapListCapabilityDataDefinition allCalculatedCap = new MapListCapabilityDataDefinition();
         if (calculatedCapabilities != null) {
             calculatedCapabilities.forEach((key1, value1) -> {
-                Map<String, ListCapabilityDataDefinition> mapByType = value1.getMapToscaDataDefinition();
-                mapByType.forEach((key, value) -> value.getListToscaDataDefinition().forEach(cap -> {
-                    cap.addToPath(componentInstance.getUniqueId());
-                    allCalculatedCap.add(key, cap);
-                }));
+                final Map<String, ListCapabilityDataDefinition> mapByType = value1.getMapToscaDataDefinition();
+                mapByType.forEach((key, value) -> value.getListToscaDataDefinition().stream()
+                    .filter(CapabilityDataDefinition::isExternal)
+                    .forEach(cap -> {
+                        cap.addToPath(componentInstance.getUniqueId());
+                        allCalculatedCap.add(key, cap);
+                    }));
             });
         }
         MapListCapabilityDataDefinition allCaps;
@@ -1068,6 +1074,93 @@ public class NodeTemplateOperation extends BaseOperation {
         return StorageOperationStatus.OK;
     }
 
+    public CapabilityDataDefinition updateComponentInstanceCapabilities(final String componentId, final String componentInstanceUniqueId,
+                                                                        final CapabilityDataDefinition capabilityDataDefinition) {
+
+        final GraphVertex containerVertex = findComponentVertex(componentId).orElse(null);
+        if (containerVertex == null) {
+            throw new OperationException(ActionStatus.COMPONENT_NOT_FOUND, componentId);
+        }
+
+        final Pair<GraphVertex, Map<String, MapListCapabilityDataDefinition>> capabilityVertexAndDataPair =
+            findCapabilityVertex(containerVertex).orElse(null);
+        if (capabilityVertexAndDataPair == null) {
+            throw new OperationException(ActionStatus.CAPABILITY_OF_INSTANCE_NOT_FOUND_ON_CONTAINER, capabilityDataDefinition.getName(),
+                capabilityDataDefinition.getOwnerName(), componentId);
+        }
+        final GraphVertex capabilitiesVertex = capabilityVertexAndDataPair.getLeft();
+        final Map<String, MapListCapabilityDataDefinition> capabilityDataMap = capabilityVertexAndDataPair.getRight();
+
+        final CapabilityDataDefinition actualCapabilityDataDefinition =
+            findCapability(capabilityDataMap, componentInstanceUniqueId, capabilityDataDefinition.getType(), capabilityDataDefinition.getUniqueId())
+                .orElse(null);
+        if (actualCapabilityDataDefinition == null) {
+            throw new OperationException(ActionStatus.CAPABILITY_OF_INSTANCE_NOT_FOUND_ON_CONTAINER, capabilityDataDefinition.getName(),
+                capabilityDataDefinition.getOwnerName(), componentId);
+        }
+        bindCapabilityDefinition(capabilityDataDefinition, actualCapabilityDataDefinition);
+
+        var storageOperationStatus = updateCapabilityVertex(containerVertex, capabilitiesVertex);
+        if (storageOperationStatus != StorageOperationStatus.OK) {
+            throw new OperationException(ActionStatus.COMPONENT_INSTANCE_CAPABILITY_UPDATE_ERROR, capabilityDataDefinition.getName());
+        }
+        return new CapabilityDataDefinition(actualCapabilityDataDefinition);
+    }
+
+    private void bindCapabilityDefinition(final CapabilityDataDefinition fromCapabilityDefinition,
+                                          final CapabilityDataDefinition capabilityDefinitionToUpdate) {
+        capabilityDefinitionToUpdate.setExternal(fromCapabilityDefinition.isExternal());
+    }
+
+    private Optional<GraphVertex> findComponentVertex(final String componentId) {
+        final Either<GraphVertex, JanusGraphOperationStatus> componentEither = janusGraphDao.getVertexById(componentId, JsonParseFlagEnum.ParseAll);
+        if (componentEither.isRight()) {
+            final JanusGraphOperationStatus error = componentEither.right().value();
+            CommonUtility.addRecordToLog(log, LogLevelEnum.DEBUG, FAILED_TO_FETCH_CONTAINER_VERTEX_ERROR, componentId, error);
+            if (error == JanusGraphOperationStatus.NOT_FOUND) {
+                return Optional.empty();
+            }
+            log.error(EcompLoggerErrorCode.BUSINESS_PROCESS_ERROR, NodeTemplateOperation.class.getName(), (ErrorLogOptionalData) null,
+                FAILED_TO_FETCH_CONTAINER_VERTEX_ERROR, componentId, error);
+            throw new OperationException(ActionStatus.COMPONENT_FIND_ERROR, componentId);
+        }
+        return Optional.ofNullable(componentEither.left().value());
+    }
+
+    private Optional<Pair<GraphVertex, Map<String, MapListCapabilityDataDefinition>>> findCapabilityVertex(final GraphVertex containerVertex) {
+
+        final Either<Pair<GraphVertex, Map<String, MapListCapabilityDataDefinition>>, StorageOperationStatus> capabilitiesEither =
+            fetchContainerCalculatedCapability(containerVertex, EdgeLabelEnum.CALCULATED_CAPABILITIES);
+        if (capabilitiesEither.isRight()) {
+            final StorageOperationStatus error = capabilitiesEither.right().value();
+            if (error == StorageOperationStatus.NOT_FOUND) {
+                return Optional.empty();
+            }
+            log.error(EcompLoggerErrorCode.BUSINESS_PROCESS_ERROR, NodeTemplateOperation.class.getName(), (ErrorLogOptionalData) null,
+                FAILED_TO_FETCH_CONTAINER_VERTEX_ERROR, containerVertex.getUniqueId(), error);
+            throw new OperationException(ActionStatus.COMPONENT_CAPABILITIES_FIND_ERROR, containerVertex.getUniqueId());
+        }
+        return Optional.ofNullable(capabilitiesEither.left().value());
+    }
+
+    private Optional<CapabilityDataDefinition> findCapability(final Map<String, MapListCapabilityDataDefinition> capabilityMap,
+                                                              final String componentInstanceUniqueId,
+                                                              final String capabilityType,
+                                                              final String capabilityUniqueId) {
+        var componentInstanceCapabilitiesMap = capabilityMap.get(componentInstanceUniqueId);
+        if (componentInstanceCapabilitiesMap == null || componentInstanceCapabilitiesMap.isEmpty()) {
+            return Optional.empty();
+        }
+        var listCapabilityDataDefinition = componentInstanceCapabilitiesMap.getMapToscaDataDefinition().get(capabilityType);
+        if (listCapabilityDataDefinition == null || listCapabilityDataDefinition.isEmpty()) {
+            return Optional.empty();
+        }
+
+        return listCapabilityDataDefinition.getListToscaDataDefinition().stream()
+            .filter(e -> e.getUniqueId().equals(capabilityUniqueId))
+            .findFirst();
+    }
+
     public StorageOperationStatus updateComponentInstanceRequirement(String componentId, String componentInstanceUniqueId,
                                                                      RequirementDataDefinition requirementDataDefinition) {
         Either<GraphVertex, JanusGraphOperationStatus> containerVEither = janusGraphDao
@@ -1143,6 +1236,27 @@ public class NodeTemplateOperation extends BaseOperation {
         return StorageOperationStatus.OK;
     }
 
+    private StorageOperationStatus updateCapabilityVertex(final GraphVertex containerVertex, final GraphVertex capabilitiesVertex) {
+        containerVertex.setJsonMetadataField(JsonPresentationFields.LAST_UPDATE_DATE, System.currentTimeMillis());
+        final Either<GraphVertex, JanusGraphOperationStatus> updateElement = janusGraphDao.updateVertex(containerVertex);
+        if (updateElement.isRight()) {
+            CommonUtility.addRecordToLog(log, LogLevelEnum.DEBUG, "Failed to update topology template '{}' with new capabilities. Error '{}'.",
+                containerVertex.getUniqueId(), updateElement.right().value());
+            return DaoStatusConverter.convertJanusGraphStatusToStorageStatus(updateElement.right().value());
+        }
+
+        final Either<GraphVertex, JanusGraphOperationStatus> statusEither =
+            updateOrCopyOnUpdate(capabilitiesVertex, containerVertex, EdgeLabelEnum.CALCULATED_CAPABILITIES);
+        if (statusEither.isRight()) {
+            JanusGraphOperationStatus error = statusEither.right().value();
+            CommonUtility.addRecordToLog(log, LogLevelEnum.DEBUG,
+                "Failed to update calculated capability for container {} error {}", containerVertex.getUniqueId(), error);
+            return DaoStatusConverter.convertJanusGraphStatusToStorageStatus(error);
+        }
+        CommonUtility.addRecordToLog(log, LogLevelEnum.DEBUG, "Updated calculated capability for container '{}'", containerVertex.getUniqueId());
+        return StorageOperationStatus.OK;
+    }
+
     private StorageOperationStatus addComponentInstanceToscaDataToNodeTypeContainer(NodeType originNodeType,
                                                                                     ComponentInstanceDataDefinition componentInstance,
                                                                                     GraphVertex updatedContainerVertex) {
index 06e23f0..205a48e 100644 (file)
@@ -112,6 +112,7 @@ import org.openecomp.sdc.be.model.catalog.CatalogComponent;
 import org.openecomp.sdc.be.model.jsonjanusgraph.config.ContainerInstanceTypesData;
 import org.openecomp.sdc.be.model.jsonjanusgraph.datamodel.TopologyTemplate;
 import org.openecomp.sdc.be.model.jsonjanusgraph.datamodel.ToscaElement;
+import org.openecomp.sdc.be.model.jsonjanusgraph.operations.exception.OperationException;
 import org.openecomp.sdc.be.model.jsonjanusgraph.utils.ModelConverter;
 import org.openecomp.sdc.be.model.operations.StorageException;
 import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus;
@@ -2963,6 +2964,12 @@ public class ToscaOperationFacade {
         return nodeTemplateOperation.updateComponentInstanceRequirement(containerComponentId, componentInstanceUniqueId, requirementDataDefinition);
     }
 
+    public CapabilityDataDefinition updateComponentInstanceCapability(final String containerComponentId, final String componentInstanceUniqueId,
+                                                                      final CapabilityDataDefinition capabilityDataDefinition) {
+
+        return nodeTemplateOperation.updateComponentInstanceCapabilities(containerComponentId, componentInstanceUniqueId, capabilityDataDefinition);
+    }
+
     public StorageOperationStatus updateComponentInstanceInterfaces(Component containerComponent, String componentInstanceUniqueId) {
         MapInterfaceDataDefinition mapInterfaceDataDefinition = convertComponentInstanceInterfaces(containerComponent, componentInstanceUniqueId);
         return topologyTemplateOperation.updateComponentInstanceInterfaces(containerComponent, componentInstanceUniqueId, mapInterfaceDataDefinition);
diff --git a/catalog-model/src/main/java/org/openecomp/sdc/be/model/jsonjanusgraph/operations/exception/OperationException.java b/catalog-model/src/main/java/org/openecomp/sdc/be/model/jsonjanusgraph/operations/exception/OperationException.java
new file mode 100644 (file)
index 0000000..30323af
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.openecomp.sdc.be.model.jsonjanusgraph.operations.exception;
+
+import lombok.Getter;
+import org.openecomp.sdc.be.dao.api.ActionStatus;
+import org.openecomp.sdc.be.exception.BusinessException;
+
+@Getter
+public class OperationException extends BusinessException {
+
+    private final ActionStatus actionStatus;
+    private final String[] params;
+
+    public OperationException(final String message) {
+        super(message);
+        actionStatus = ActionStatus.GENERAL_ERROR;
+        params = new String[0];
+    }
+
+    public OperationException(final ActionStatus actionStatus, String... params) {
+        this.actionStatus = actionStatus;
+        this.params = params;
+    }
+}
diff --git a/catalog-model/src/main/java/org/openecomp/sdc/be/ui/mapper/CapabilityMapper.java b/catalog-model/src/main/java/org/openecomp/sdc/be/ui/mapper/CapabilityMapper.java
new file mode 100644 (file)
index 0000000..4df9382
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.openecomp.sdc.be.ui.mapper;
+
+import org.openecomp.sdc.be.model.CapabilityDefinition;
+import org.openecomp.sdc.be.ui.model.ComponentInstanceCapabilityUpdateModel;
+import org.springframework.stereotype.Service;
+
+@Service
+public class CapabilityMapper {
+
+    public CapabilityDefinition mapToCapabilityDefinition(final ComponentInstanceCapabilityUpdateModel capabilityUpdateModel) {
+        var capabilityDefinition = new CapabilityDefinition();
+        capabilityDefinition.setUniqueId(capabilityUpdateModel.getUniqueId());
+        capabilityDefinition.setType(capabilityUpdateModel.getType());
+        capabilityDefinition.setOwnerId(capabilityUpdateModel.getOwnerId());
+        capabilityDefinition.setOwnerName(capabilityUpdateModel.getOwnerName());
+        capabilityDefinition.setName(capabilityUpdateModel.getName());
+        capabilityDefinition.setExternal(capabilityUpdateModel.getExternal());
+        return capabilityDefinition;
+    }
+
+}
diff --git a/catalog-model/src/main/java/org/openecomp/sdc/be/ui/model/ComponentInstanceCapabilityUpdateModel.java b/catalog-model/src/main/java/org/openecomp/sdc/be/ui/model/ComponentInstanceCapabilityUpdateModel.java
new file mode 100644 (file)
index 0000000..3fd74cd
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.openecomp.sdc.be.ui.model;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import lombok.Data;
+
+/**
+ * Model for the component instance capability update request
+ */
+@Data
+public class ComponentInstanceCapabilityUpdateModel {
+
+    @NotNull
+    @Size(min=1)
+    private String type;
+    @NotNull
+    @Size(min=1)
+    private String name;
+    @NotNull
+    @Size(min=1)
+    private String ownerId;
+    @NotNull
+    @Size(min=1)
+    private String ownerName;
+    @NotNull
+    @Size(min=1)
+    private String uniqueId;
+    @NotNull
+    private Boolean external;
+
+}
index cf4d6b2..5a02c57 100644 (file)
  */
 package org.openecomp.sdc.be.model.jsonjanusgraph.operations;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import com.google.common.collect.Lists;
 import fj.data.Either;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
 import org.apache.commons.lang3.tuple.ImmutablePair;
 import org.apache.tinkerpop.gremlin.structure.Direction;
 import org.apache.tinkerpop.gremlin.structure.Edge;
-import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.janusgraph.core.JanusGraphVertex;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
@@ -43,46 +60,48 @@ import org.junit.jupiter.api.TestInstance.Lifecycle;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.ArgumentMatchers;
 import org.mockito.InjectMocks;
-import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.mockito.junit.MockitoJUnitRunner;
 import org.mockito.junit.jupiter.MockitoExtension;
+import org.openecomp.sdc.be.config.ConfigurationManager;
+import org.openecomp.sdc.be.dao.api.ActionStatus;
 import org.openecomp.sdc.be.dao.janusgraph.JanusGraphOperationStatus;
 import org.openecomp.sdc.be.dao.jsongraph.GraphVertex;
 import org.openecomp.sdc.be.dao.jsongraph.JanusGraphDao;
 import org.openecomp.sdc.be.dao.jsongraph.types.EdgeLabelEnum;
 import org.openecomp.sdc.be.dao.jsongraph.types.JsonParseFlagEnum;
 import org.openecomp.sdc.be.dao.jsongraph.types.VertexTypeEnum;
-import org.openecomp.sdc.be.datatypes.elements.*;
+import org.openecomp.sdc.be.datatypes.elements.ArtifactDataDefinition;
+import org.openecomp.sdc.be.datatypes.elements.CapabilityDataDefinition;
+import org.openecomp.sdc.be.datatypes.elements.ComponentInstanceDataDefinition;
+import org.openecomp.sdc.be.datatypes.elements.ListCapabilityDataDefinition;
+import org.openecomp.sdc.be.datatypes.elements.ListRequirementDataDefinition;
+import org.openecomp.sdc.be.datatypes.elements.MapArtifactDataDefinition;
+import org.openecomp.sdc.be.datatypes.elements.MapListCapabilityDataDefinition;
+import org.openecomp.sdc.be.datatypes.elements.MapListRequirementDataDefinition;
+import org.openecomp.sdc.be.datatypes.elements.RequirementDataDefinition;
 import org.openecomp.sdc.be.datatypes.enums.GraphPropertyEnum;
 import org.openecomp.sdc.be.datatypes.enums.JsonPresentationFields;
 import org.openecomp.sdc.be.datatypes.enums.ResourceTypeEnum;
-import org.openecomp.sdc.be.datatypes.tosca.ToscaDataDefinition;
-import org.openecomp.sdc.be.model.*;
+import org.openecomp.sdc.be.model.ArtifactDefinition;
+import org.openecomp.sdc.be.model.CapabilityDefinition;
+import org.openecomp.sdc.be.model.CapabilityRequirementRelationship;
+import org.openecomp.sdc.be.model.Component;
+import org.openecomp.sdc.be.model.ComponentInstance;
+import org.openecomp.sdc.be.model.ComponentInstanceAttribute;
+import org.openecomp.sdc.be.model.ComponentInstanceOutput;
+import org.openecomp.sdc.be.model.GroupDefinition;
+import org.openecomp.sdc.be.model.ModelTestBase;
+import org.openecomp.sdc.be.model.RelationshipImpl;
+import org.openecomp.sdc.be.model.RelationshipInfo;
+import org.openecomp.sdc.be.model.RequirementCapabilityRelDef;
+import org.openecomp.sdc.be.model.Resource;
+import org.openecomp.sdc.be.model.User;
 import org.openecomp.sdc.be.model.jsonjanusgraph.datamodel.TopologyTemplate;
 import org.openecomp.sdc.be.model.jsonjanusgraph.datamodel.ToscaElement;
+import org.openecomp.sdc.be.model.jsonjanusgraph.operations.exception.OperationException;
 import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus;
 import org.openecomp.sdc.common.api.ArtifactGroupTypeEnum;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import static org.codehaus.groovy.runtime.DefaultGroovyMethods.any;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertSame;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.mockito.ArgumentMatchers.anyList;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
 @ExtendWith(MockitoExtension.class)
 @TestInstance(Lifecycle.PER_CLASS)
 class NodeTemplateOperationTest extends ModelTestBase {
@@ -205,10 +224,8 @@ class NodeTemplateOperationTest extends ModelTestBase {
 
     @Test
     void testGetDefaultHeatTimeout() {
-        Integer result;
-
-        // default test
-        result = NodeTemplateOperation.getDefaultHeatTimeout();
+        assertEquals(ConfigurationManager.getConfigurationManager().getConfiguration().getHeatArtifactDeploymentTimeout().getDefaultMinutes(),
+            NodeTemplateOperation.getDefaultHeatTimeout());
     }
 
     @Test
@@ -234,13 +251,8 @@ class NodeTemplateOperationTest extends ModelTestBase {
     }
 
     @Test
-    void testCreateCapPropertyKey() throws Exception {
-        String key = "";
-        String instanceId = "";
-        String result;
-
-        // default test
-        result = NodeTemplateOperation.createCapPropertyKey(key, instanceId);
+    void testCreateCapPropertyKey() {
+        assertEquals("instanceId#instanceId#key", NodeTemplateOperation.createCapPropertyKey("key", "instanceId"));
     }
 
     @Test
@@ -438,6 +450,119 @@ class NodeTemplateOperationTest extends ModelTestBase {
                 eq(componentInstanceOutputList), ArgumentMatchers.anyList(), eq(JsonPresentationFields.NAME));
     }
 
+    @Test
+    void updateComponentInstanceCapabilitiesSuccessTest() {
+        final var capabilityUniqueId = "capabilityUniqueId";
+        final var capabilityType = "capabilityType";
+        final var componentInstanceId = "componentInstanceId";
+
+        final var nodeTemplateOperation = spy(operation);
+        final var capabilityDefinitionDatabase = new CapabilityDataDefinition();
+        capabilityDefinitionDatabase.setUniqueId(capabilityUniqueId);
+        capabilityDefinitionDatabase.setType(capabilityType);
+        capabilityDefinitionDatabase.setExternal(false);
+        final var capabilityDefinitionToUpdate = new CapabilityDataDefinition();
+        capabilityDefinitionToUpdate.setUniqueId(capabilityUniqueId);
+        capabilityDefinitionToUpdate.setType(capabilityType);
+        capabilityDefinitionToUpdate.setExternal(true);
+        final Map<String, MapListCapabilityDataDefinition> capabilityByInstanceMap = new HashMap<>();
+        var mapListCapabilityDataDefinition = new MapListCapabilityDataDefinition();
+        mapListCapabilityDataDefinition.add(capabilityDefinitionDatabase.getType(), capabilityDefinitionDatabase);
+        capabilityByInstanceMap.put(componentInstanceId, mapListCapabilityDataDefinition);
+
+        final GraphVertex componentVertex = Mockito.mock(GraphVertex.class);
+        final GraphVertex capabilitiesVertex = Mockito.mock(GraphVertex.class);
+        doReturn(capabilityByInstanceMap).when(capabilitiesVertex).getJson();
+        when(janusGraphDao.getVertexById(COMPONENT_ID, JsonParseFlagEnum.ParseAll)).thenReturn(Either.left(componentVertex));
+        when(janusGraphDao.getChildVertex(componentVertex, EdgeLabelEnum.CALCULATED_CAPABILITIES, JsonParseFlagEnum.ParseJson))
+            .thenReturn(Either.left(capabilitiesVertex));
+        when(janusGraphDao.updateVertex(componentVertex)).thenReturn(Either.left(componentVertex));
+        doReturn(Either.left(capabilitiesVertex))
+            .when(nodeTemplateOperation).updateOrCopyOnUpdate(capabilitiesVertex, componentVertex, EdgeLabelEnum.CALCULATED_CAPABILITIES);
+        final CapabilityDataDefinition actualCapabilityDataDefinition = nodeTemplateOperation
+            .updateComponentInstanceCapabilities(COMPONENT_ID, componentInstanceId, capabilityDefinitionToUpdate);
+        assertTrue(actualCapabilityDataDefinition.isExternal());
+    }
+
+    @Test
+    void updateComponentInstanceCapabilitiesTest_capabilityNotFound() {
+        final var componentInstanceId = "componentInstanceId";
+        var capabilityDataDefinition = new CapabilityDataDefinition();
+        capabilityDataDefinition.setType("aCapabilityType");
+        final GraphVertex componentVertex = Mockito.mock(GraphVertex.class);
+        final GraphVertex calculatedCapabilityVertex = Mockito.mock(GraphVertex.class);
+        //no capabilities found
+        when(janusGraphDao.getVertexById(COMPONENT_ID, JsonParseFlagEnum.ParseAll)).thenReturn(Either.left(componentVertex));
+        when(janusGraphDao.getChildVertex(componentVertex, EdgeLabelEnum.CALCULATED_CAPABILITIES, JsonParseFlagEnum.ParseJson))
+            .thenReturn(Either.right(JanusGraphOperationStatus.NOT_FOUND));
+        OperationException actualException = assertThrows(OperationException.class, () ->
+            operation.updateComponentInstanceCapabilities(COMPONENT_ID, componentInstanceId, capabilityDataDefinition));
+        assertEquals(ActionStatus.CAPABILITY_OF_INSTANCE_NOT_FOUND_ON_CONTAINER, actualException.getActionStatus());
+        assertEquals(actualException.getParams().length, 3);
+        assertEquals(capabilityDataDefinition.getName(), actualException.getParams()[0]);
+        assertEquals(capabilityDataDefinition.getOwnerName(), actualException.getParams()[1]);
+        assertEquals(COMPONENT_ID, actualException.getParams()[2]);
+
+        //found capabilities, but not for the provided instance id
+        when(janusGraphDao.getChildVertex(componentVertex, EdgeLabelEnum.CALCULATED_CAPABILITIES, JsonParseFlagEnum.ParseJson))
+            .thenReturn(Either.left(calculatedCapabilityVertex));
+        actualException = assertThrows(OperationException.class, () ->
+            operation.updateComponentInstanceCapabilities(COMPONENT_ID, "componentInstanceId", capabilityDataDefinition));
+        assertEquals(ActionStatus.CAPABILITY_OF_INSTANCE_NOT_FOUND_ON_CONTAINER, actualException.getActionStatus());
+        assertEquals(actualException.getParams().length, 3);
+        assertEquals(capabilityDataDefinition.getName(), actualException.getParams()[0]);
+        assertEquals(capabilityDataDefinition.getOwnerName(), actualException.getParams()[1]);
+        assertEquals(COMPONENT_ID, actualException.getParams()[2]);
+
+        //found capabilities for the instance id, but not with the provided capability type
+        final Map<String, MapListCapabilityDataDefinition> capabilityByInstanceMap = new HashMap<>();
+        var mapListCapabilityDataDefinition = new MapListCapabilityDataDefinition();
+        mapListCapabilityDataDefinition.add("anotherCapabilityType", new CapabilityDataDefinition());
+        capabilityByInstanceMap.put(componentInstanceId, mapListCapabilityDataDefinition);
+        doReturn(capabilityByInstanceMap).when(calculatedCapabilityVertex).getJson();
+        actualException = assertThrows(OperationException.class, () ->
+            operation.updateComponentInstanceCapabilities(COMPONENT_ID, "componentInstanceId", capabilityDataDefinition));
+        assertEquals(ActionStatus.CAPABILITY_OF_INSTANCE_NOT_FOUND_ON_CONTAINER, actualException.getActionStatus());
+        assertEquals(actualException.getParams().length, 3);
+        assertEquals(capabilityDataDefinition.getName(), actualException.getParams()[0]);
+        assertEquals(capabilityDataDefinition.getOwnerName(), actualException.getParams()[1]);
+        assertEquals(COMPONENT_ID, actualException.getParams()[2]);
+    }
+
+    @Test
+    void updateComponentInstanceCapabilitiesTest_capabilityFindError() {
+        final GraphVertex componentVertex = Mockito.mock(GraphVertex.class);
+        when(componentVertex.getUniqueId()).thenReturn(COMPONENT_ID);
+        when(janusGraphDao.getVertexById(COMPONENT_ID, JsonParseFlagEnum.ParseAll)).thenReturn(Either.left(componentVertex));
+        when(janusGraphDao.getChildVertex(componentVertex, EdgeLabelEnum.CALCULATED_CAPABILITIES, JsonParseFlagEnum.ParseJson))
+            .thenReturn(Either.right(JanusGraphOperationStatus.GENERAL_ERROR));
+        final OperationException actualException = assertThrows(OperationException.class, () ->
+            operation.updateComponentInstanceCapabilities(COMPONENT_ID, "componentInstanceId", new CapabilityDataDefinition()));
+        assertEquals(ActionStatus.COMPONENT_CAPABILITIES_FIND_ERROR, actualException.getActionStatus());
+        assertEquals(actualException.getParams().length, 1);
+        assertEquals(COMPONENT_ID, actualException.getParams()[0]);
+    }
+
+    @Test
+    void updateComponentInstanceCapabilitiesTest_componentNotFound() {
+        when(janusGraphDao.getVertexById(COMPONENT_ID, JsonParseFlagEnum.ParseAll)).thenReturn(Either.right(JanusGraphOperationStatus.NOT_FOUND));
+        final OperationException actualException = assertThrows(OperationException.class, () ->
+            operation.updateComponentInstanceCapabilities(COMPONENT_ID, "componentInstanceId", new CapabilityDataDefinition()));
+        assertEquals(ActionStatus.COMPONENT_NOT_FOUND, actualException.getActionStatus());
+        assertEquals(actualException.getParams().length, 1);
+        assertEquals(COMPONENT_ID, actualException.getParams()[0]);
+    }
+
+    @Test
+    void updateComponentInstanceCapabilitiesTest_componentFindError() {
+        when(janusGraphDao.getVertexById(COMPONENT_ID, JsonParseFlagEnum.ParseAll)).thenReturn(Either.right(JanusGraphOperationStatus.GENERAL_ERROR));
+        final OperationException actualException = assertThrows(OperationException.class, () ->
+            operation.updateComponentInstanceCapabilities(COMPONENT_ID, "componentInstanceId", new CapabilityDataDefinition()));
+        assertEquals(ActionStatus.COMPONENT_FIND_ERROR, actualException.getActionStatus());
+        assertEquals(actualException.getParams().length, 1);
+        assertEquals(COMPONENT_ID, actualException.getParams()[0]);
+    }
+
     private ComponentInstance createCompInstance() {
         ComponentInstance componentInstance = new ComponentInstance();
         String id = "id";
diff --git a/catalog-model/src/test/java/org/openecomp/sdc/be/ui/mapper/CapabilityMapperTest.java b/catalog-model/src/test/java/org/openecomp/sdc/be/ui/mapper/CapabilityMapperTest.java
new file mode 100644 (file)
index 0000000..6e14728
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.openecomp.sdc.be.ui.mapper;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.openecomp.sdc.be.model.CapabilityDefinition;
+import org.openecomp.sdc.be.ui.model.ComponentInstanceCapabilityUpdateModel;
+
+class CapabilityMapperTest {
+
+    private CapabilityMapper capabilityMapper;
+
+    @BeforeEach
+    void beforeEach() {
+        capabilityMapper = new CapabilityMapper();
+    }
+
+    @Test
+    void mapToCapabilityDefinitionTest() {
+        final ComponentInstanceCapabilityUpdateModel capabilityUpdateModel = new ComponentInstanceCapabilityUpdateModel();
+        capabilityUpdateModel.setUniqueId("uniqueId");
+        capabilityUpdateModel.setName("name");
+        capabilityUpdateModel.setExternal(true);
+        capabilityUpdateModel.setOwnerId("ownerId");
+        capabilityUpdateModel.setType("type");
+        capabilityUpdateModel.setOwnerName("ownerName");
+        final CapabilityDefinition capabilityDefinition = capabilityMapper.mapToCapabilityDefinition(capabilityUpdateModel);
+        assertCapabilityDefinition(capabilityDefinition, capabilityUpdateModel);
+    }
+
+    private void assertCapabilityDefinition(final CapabilityDefinition actualCapabilityDefinition,
+                                            final ComponentInstanceCapabilityUpdateModel expectedCapabilityDefinition) {
+        assertEquals(expectedCapabilityDefinition.getUniqueId(), actualCapabilityDefinition.getUniqueId());
+        assertEquals(expectedCapabilityDefinition.getName(), actualCapabilityDefinition.getName());
+        assertEquals(expectedCapabilityDefinition.getOwnerId(), actualCapabilityDefinition.getOwnerId());
+        assertEquals(expectedCapabilityDefinition.getOwnerName(), actualCapabilityDefinition.getOwnerName());
+        assertEquals(expectedCapabilityDefinition.getType(), actualCapabilityDefinition.getType());
+        assertEquals(expectedCapabilityDefinition.getExternal(), actualCapabilityDefinition.isExternal());
+    }
+}
\ No newline at end of file
index f365dc4..b377ed9 100644 (file)
@@ -62,6 +62,7 @@ export class Capability implements RequirementCapabilityModel {
     description: string;
     validSourceTypes: string[];
     properties: PropertyModel[];
+    external: boolean;
 
     // custom
     selected: boolean;
@@ -84,6 +85,7 @@ export class Capability implements RequirementCapabilityModel {
             this.description = capability.description;
             this.validSourceTypes = capability.validSourceTypes;
             this.selected = capability.selected;
+            this.external = capability.external;
             this.initFilterTerm();
 
         }
index 3cab4b3..8d2357d 100644 (file)
@@ -25,7 +25,8 @@ import {
     ZoneInstanceAssignmentType,
     ZoneInstanceMode,
     ZoneInstanceType,
-    Requirement
+    Requirement,
+    Capability
 } from 'app/models';
 import { ForwardingPath } from 'app/models/forwarding-path';
 import { CompositionCiServicePathLink } from 'app/models/graph/graph-links/composition-graph-links/composition-ci-service-path-link';
@@ -649,6 +650,14 @@ export class CompositionGraphComponent implements AfterViewInit {
                 .find(r => r.uniqueId === requirement.uniqueId).external = requirement.external;
         });
 
+        this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_COMPONENT_INSTANCE_CAPABILITY_EXTERNAL_CHANGED,
+            (uniqueId: string, capability: Capability) => {
+                const graphCapability = this._cy.getElementById(uniqueId).data()
+                    .componentInstance.capabilities[capability.type].find(cap => cap.uniqueId === capability.uniqueId);
+                graphCapability.external = capability.external;
+            }
+        );
+
         this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, (componentInstanceId: string) => {
             const nodeToDelete = this._cy.getElementById(componentInstanceId);
             this.nodesGraphUtils.deleteNode(this._cy, this.topologyTemplate, nodeToDelete);
index c73f697..ad25aab 100644 (file)
@@ -5,6 +5,16 @@
                 <div *ngFor="let capability of capabilities" class="relations-details-container">
                     <div class="relations-name">{{capability.name}}&nbsp;</div>
                     <div class="relations-desc"> {{capability.type}} </div>
+                    <div class="checkbox-label-mark-as-external" *ngIf="isComponentInstanceSelected">
+                        <checkbox
+                            class="checkbox-label"
+                            [attr.data-tests-id]="'checkbox-external-cap-' + capability.name"
+                            [label]="'External'"
+                            (checkedChange)="onMarkCapabilityAsExternal(capability)"
+                            [(checked)]="capability.external"
+                            [disabled]="isViewOnly">
+                        </checkbox>
+                    </div>
                 </div>
             </sdc-accordion>
         </div>
     <ng-template #complexComponentTemplate>
         <sdc-accordion *ngIf="capabilitiesInstancesMap" [title]="'Capabilities'" [arrow-direction]="'right'" [testId]="'Capabilities-accordion'">
             <sdc-accordion *ngFor="let key of objectKeys(capabilitiesInstancesMap); let i = index" [title]="key">
-                    <div *ngFor="let capability of capabilitiesInstancesMap[key]" class="relations-details-container">
-                            <div class="relations-name">{{capability.name}}&nbsp;</div>
-                            <div class="relations-desc"> {{capability.type}} </div>
-                        </div>
+                <div *ngFor="let capability of capabilitiesInstancesMap[key]" class="relations-details-container">
+                    <div class="relations-name">{{capability.name}}&nbsp;</div>
+                    <div class="relations-desc"> {{capability.type}} </div>
+                    <div class="checkbox-label-mark-as-external" *ngIf="isComponentInstanceSelected">
+                        <checkbox
+                            class="checkbox-label"
+                            [attr.data-tests-id]="'checkbox-external-cap-' + capability.name"
+                            [label]="'External'"
+                            (checkedChange)="onMarkCapabilityAsExternal(capability)"
+                            [(checked)]="capability.external"
+                            [disabled]="isViewOnly">
+                        </checkbox>
+                    </div>
+                </div>
             </sdc-accordion>
         </sdc-accordion>
 
index 7c91cbc..7bb88c7 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, OnInit, Input, OnDestroy } from '@angular/core';
-import { Component as TopologyTemplate, Capability, Requirement, CapabilitiesGroup, RequirementsGroup, ComponentInstance, FullComponentInstance } from "app/models";
+import { Component as TopologyTemplate, Capability, Requirement, CapabilitiesGroup, RequirementsGroup, FullComponentInstance } from "app/models";
 import { Store } from "@ngxs/store";
 import { GRAPH_EVENTS } from "app/utils";
 import { ComponentGenericResponse } from "app/ng2/services/responses/component-generic-response";
@@ -8,6 +8,7 @@ import { EventListenerService } from "app/services";
 import { WorkspaceService } from "app/ng2/pages/workspace/workspace.service";
 import { CompositionService } from "app/ng2/pages/composition/composition.service";
 import {SelectedComponentType, TogglePanelLoadingAction} from "../../../common/store/graph.actions";
+import {ComponentInstanceServiceNg2} from "../../../../../services/component-instance-services/component-instance.service";
 
 
 export class InstanceCapabilitiesMap {
@@ -42,7 +43,8 @@ export class ReqAndCapabilitiesTabComponent implements OnInit, OnDestroy {
         private topologyTemplateService:TopologyTemplateService,
         private workspaceService: WorkspaceService,
         private compositionService: CompositionService,
-        private eventListenerService:EventListenerService) { }
+        private eventListenerService:EventListenerService,
+        private componentInstanceService: ComponentInstanceServiceNg2) { }
 
     ngOnInit(): void {
 
@@ -112,13 +114,19 @@ export class ReqAndCapabilitiesTabComponent implements OnInit, OnDestroy {
     private initInstancesMap = ():void => {
 
         this.capabilitiesInstancesMap = new InstanceCapabilitiesMap();
-        _.forEach(this.capabilities, (capability:Capability) => {
-            if (this.capabilitiesInstancesMap[capability.ownerName]) {
-                this.capabilitiesInstancesMap[capability.ownerName] = this.capabilitiesInstancesMap[capability.ownerName].concat(capability);
-            } else {
-                this.capabilitiesInstancesMap[capability.ownerName] = new Array<Capability>(capability);
+        let capabilityList: Array<Capability> = this.capabilities;
+        if (capabilityList) {
+            if (!this.isComponentInstanceSelected) {
+                capabilityList = capabilityList.filter(value => value.external);
             }
-        });
+            capabilityList.forEach(capability => {
+                if (this.capabilitiesInstancesMap[capability.ownerName]) {
+                    this.capabilitiesInstancesMap[capability.ownerName] = this.capabilitiesInstancesMap[capability.ownerName].concat(capability);
+                } else {
+                    this.capabilitiesInstancesMap[capability.ownerName] = new Array<Capability>(capability);
+                }
+            });
+        }
 
         this.requirementsInstancesMap = new InstanceRequirementsMap();
         _.forEach(this.requirements, (requirement:Requirement) => {
@@ -161,7 +169,21 @@ export class ReqAndCapabilitiesTabComponent implements OnInit, OnDestroy {
     }
 
 
-
-
+    onMarkCapabilityAsExternal(capability: Capability) {
+        this.store.dispatch(new TogglePanelLoadingAction({isLoading: true}));
+        capability.external = !capability.external;
+        const componentId = this.workspaceService.metadata.uniqueId;
+        const componentInstanceId = this.component.uniqueId;
+        this.componentInstanceService
+        .updateInstanceCapability(this.workspaceService.metadata.getTypeUrl(), componentId, componentInstanceId, capability)
+        .subscribe(() => {
+            this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_COMPONENT_INSTANCE_CAPABILITY_EXTERNAL_CHANGED, componentInstanceId, capability);
+            this.store.dispatch(new TogglePanelLoadingAction({isLoading: false}));
+        } , (error) => {
+            console.error("An error has occurred while setting capability '" + capability.name + "' external", error);
+            capability.external = !capability.external;
+            this.store.dispatch(new TogglePanelLoadingAction({isLoading: false}));
+        });
+    }
 }
 
index ebaed04..ee82779 100644 (file)
@@ -17,7 +17,7 @@
                          *ngIf="isInstanceSelected">
                         <checkbox
                                 class="checkbox-label"
-                                [attr.data-tests-id]="'checkbox-mark-as-external-' + requirement.name"
+                                [attr.data-tests-id]="'checkbox-external-req-' + requirement.name"
                                 [label]="'External'"
                                 (checkedChange)="onMarkAsExternal(requirement)"
                                 [(checked)]="requirement.external"
diff --git a/catalog-ui/src/app/ng2/services/component-instance-services/component-instance.service.spec.ts b/catalog-ui/src/app/ng2/services/component-instance-services/component-instance.service.spec.ts
new file mode 100644 (file)
index 0000000..4de556c
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+import {TestBed} from '@angular/core/testing';
+import {ISdcConfig, SdcConfigToken} from "../../config/sdc-config.config";
+import {ComponentInstanceServiceNg2} from "./component-instance.service";
+import {Capability} from "../../../models/capability";
+import {HttpClientTestingModule, HttpTestingController} from "@angular/common/http/testing";
+
+describe('ComponentInstanceServiceNg2', () => {
+  let httpTestingController: HttpTestingController;
+  let componentInstanceService: ComponentInstanceServiceNg2;
+  let rootApi: string = 'http://localhost/'
+  let componentApiRoot: string = 'catalog/'
+  beforeEach(() => {
+    const sdcConfigToken: Partial<ISdcConfig> = {
+      'api': {
+        'root': rootApi,
+        'component_api_root': componentApiRoot,
+      }
+    };
+    TestBed.configureTestingModule({
+      providers: [ComponentInstanceServiceNg2,
+        {provide: SdcConfigToken, useValue: sdcConfigToken}
+      ],
+      imports: [HttpClientTestingModule]
+    });
+    httpTestingController = TestBed.get(HttpTestingController);
+    componentInstanceService = TestBed.get(ComponentInstanceServiceNg2);
+  });
+
+  it('should be created', () => {
+    expect(componentInstanceService).toBeTruthy();
+  });
+
+  it('updateInstanceCapability call should return the expected data', () => {
+    const capabilityToUpdate = new Capability();
+    capabilityToUpdate.type = "tosca.capabilities.Scalable";
+    capabilityToUpdate.name = "capScalable";
+    capabilityToUpdate.ownerId = "191f8a83-d362-4db4-af30-75d71a55c959.a822dd1c-3560-47ea-b8a2-f557fed5e186.vfcapreq10";
+    capabilityToUpdate.uniqueId = "2047eb3c-de31-4413-a358-8710a3dd2670";
+    capabilityToUpdate.external = true;
+
+    const componentTypeUrl = "services/";
+    let actualCapability: Capability;
+    componentInstanceService.updateInstanceCapability(componentTypeUrl, "componentId", "componentInstanceId", capabilityToUpdate)
+    .subscribe(capability => {
+      actualCapability = capability;
+    });
+
+    const request =
+        httpTestingController.expectOne(`${rootApi}${componentApiRoot}${componentTypeUrl}componentId/componentInstances/componentInstanceId/capability/`);
+
+    expect(request.request.method).toEqual('PUT');
+
+    request.flush(capabilityToUpdate);
+    expect(actualCapability.name).toEqual(capabilityToUpdate.name);
+    expect(actualCapability.type).toEqual(capabilityToUpdate.type);
+    expect(actualCapability.ownerId).toEqual(capabilityToUpdate.ownerId);
+    expect(actualCapability.uniqueId).toEqual(capabilityToUpdate.uniqueId);
+    expect(actualCapability.external).toEqual(capabilityToUpdate.external);
+  });
+
+});
index 5ae2918..1e4ddda 100644 (file)
  * ============LICENSE_END=========================================================
  */
 
-import {Injectable, Inject} from '@angular/core';
-import { Observable } from 'rxjs/Observable';
-import {PropertyFEModel, PropertyBEModel, Requirement} from "app/models";
-import {CommonUtils, ComponentType, ServerTypeUrl, ComponentInstanceFactory} from "app/utils";
-import {Component, ComponentInstance, Capability, PropertyModel, ArtifactGroupModel, ArtifactModel, AttributeModel, IFileDownload} from "app/models";
-import {SdcConfigToken, ISdcConfig} from "../../config/sdc-config.config";
-import { HttpClient, HttpHeaders } from '@angular/common/http';
-import { InputBEModel } from '../../../models/properties-inputs/input-be-model';
-import { HttpHelperService } from '../http-hepler.service';
+import {Inject, Injectable} from '@angular/core';
+import {Observable} from 'rxjs/Observable';
+import {
+    ArtifactGroupModel,
+    ArtifactModel,
+    AttributeModel,
+    Capability,
+    Component,
+    ComponentInstance,
+    IFileDownload,
+    PropertyBEModel,
+    PropertyModel,
+    Requirement
+} from "app/models";
+import {CommonUtils, ComponentInstanceFactory, ComponentType, ServerTypeUrl} from "app/utils";
+import {ISdcConfig, SdcConfigToken} from "../../config/sdc-config.config";
+import {HttpClient, HttpHeaders} from '@angular/common/http';
+import {InputBEModel} from '../../../models/properties-inputs/input-be-model';
+import {HttpHelperService} from '../http-hepler.service';
 import {AttributeBEModel} from "../../../models/attributes-outputs/attribute-be-model";
 import {OutputBEModel} from "../../../models/attributes-outputs/output-be-model";
 
@@ -158,6 +168,11 @@ export class ComponentInstanceServiceNg2 {
             '/requirementName/' +  requirement.name, requirement);
     }
 
+    updateInstanceCapability(componentTypeUrl: string, componentId: string, componentInstanceId: string, capability: Capability): Observable<Capability> {
+        const url = `${this.baseUrl}${componentTypeUrl}${componentId}/componentInstances/${componentInstanceId}/capability/`;
+        return this.http.put<Capability>(url, capability);
+    }
+
     updateInstanceInputs(component: Component, componentInstanceId: string, inputs: PropertyBEModel[]): Observable<PropertyBEModel[]> {
 
         return this.http.post<Array<PropertyModel>>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/resourceInstance/' + componentInstanceId + '/inputs', inputs)
@@ -203,7 +218,6 @@ export class ComponentInstanceServiceNg2 {
     }
 
     addInstanceArtifact = (componentType:string, componentId:string, instanceId:string, artifact:ArtifactModel):Observable<ArtifactModel> => {
-        // let deferred = this.$q.defer<ArtifactModel>();
         let headerObj = new HttpHeaders();
         if (artifact.payloadData) {
             headerObj = headerObj.set('Content-MD5', HttpHelperService.getHeaderMd5(artifact));
@@ -217,7 +231,7 @@ export class ComponentInstanceServiceNg2 {
     updateInstanceArtifact = (componentType:string, componentId:string, instanceId:string, artifact:ArtifactModel):Observable<ArtifactModel> => {
         return this.http.post<ArtifactModel>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/resourceInstance/' + instanceId + '/artifacts/' + artifact.uniqueId, artifact).map((res) => {
             return new ArtifactModel(res);
-        });;
+        });
     };
 
     deleteInstanceArtifact = (componentId:string, componentType:string, instanceId:string, artifactId:string, artifactLabel:string):Observable<ArtifactModel> => {
index 3e86ec9..f7cbf8d 100644 (file)
@@ -349,6 +349,7 @@ export class GRAPH_EVENTS {
   static ON_PALETTE_COMPONENT_HIDE_POPUP_PANEL = 'onPaletteComponentHidePopupPanel';
   static ON_COMPONENT_INSTANCE_NAME_CHANGED = 'onComponentInstanceNameChanged';
   static ON_COMPONENT_INSTANCE_REQUIREMENT_EXTERNAL_CHANGED = 'onComponentInstanceRequirementExternalChanged'
+  static ON_COMPONENT_INSTANCE_CAPABILITY_EXTERNAL_CHANGED = 'onComponentInstanceCapabilityExternalChanged'
   static ON_ZONE_INSTANCE_NAME_CHANGED = 'onZoneInstanceNameChanged';
   static ON_DELETE_COMPONENT_INSTANCE = 'onDeleteComponentInstance';
   static ON_DELETE_ZONE_INSTANCE = 'onDeleteZoneInstance';
index b046742..2840ccd 100644 (file)
@@ -19,6 +19,8 @@
  */
 package org.openecomp.sdc.exception;
 
+import lombok.Setter;
+
 /**
  * Nested POJOs to express required JSON format of the error
  * <p>
@@ -28,6 +30,7 @@ package org.openecomp.sdc.exception;
  */
 public class ResponseFormat {
 
+    @Setter
     private int status;
     private RequestErrorWrapper requestErrorWrapper;
 
@@ -40,10 +43,6 @@ public class ResponseFormat {
         this.status = status;
     }
 
-    public void setStatus(int status) {
-        this.status = status;
-    }
-
     public Integer getStatus() {
         return status;
     }
@@ -153,9 +152,6 @@ public class ResponseFormat {
         @SuppressWarnings("unused")
         private OkResponseInfo okResponseInfo;
 
-        public RequestError() {
-        }
-
         public PolicyException getPolicyException() {
             return policyException;
         }
index 88b6dbf..253a41f 100644 (file)
@@ -79,6 +79,7 @@ public enum LoggerSupportabilityActions {
     UPDATE_GROUP_MEMBERS("UPDATE GROUP MEMBERS"),
     UPDATE_INSTANCE_CAPABILITY_PROPERTY("UPDATE INSTANCE CAPABILITY PROPERTY"),
     UPDATE_INSTANCE_REQUIREMENT("UPDATE INSTANCE REQUIREMENT"),
+    UPDATE_INSTANCE_CAPABILITY("UPDATE INSTANCE CAPABILITY"),
     UPDATE_POLICY_TARGET("UPDATE POLICY TARGET"),
     UPDATE_POLICIES_PROPERTIES("UPDATE POLICIES PROPERTIES");
     // @formatter:on
index 78bfe50..3345c95 100644 (file)
@@ -21,6 +21,8 @@
 package org.openecomp.sdc.be.datatypes.elements;
 
 import com.google.common.collect.Lists;
+import lombok.Getter;
+import lombok.Setter;
 import org.openecomp.sdc.be.datatypes.enums.JsonPresentationFields;
 import org.openecomp.sdc.be.datatypes.tosca.ToscaDataDefinition;
 
@@ -35,6 +37,10 @@ public class CapabilityDataDefinition extends ToscaDataDefinition {
     public static final String MIN_OCCURRENCES = "0";
     public static final String MAX_OCCURRENCES = "UNBOUNDED";
 
+    @Getter
+    @Setter
+    private boolean external = false;
+
     /**
      * The default constructor initializing limits of the occurrences
      */
@@ -45,11 +51,11 @@ public class CapabilityDataDefinition extends ToscaDataDefinition {
     }
 
     /**
-     * Deep copy constructor
+     * Deep copy constructor.
      *
-     * @param other
+     * @param other the capability data definition to copy
      */
-    public CapabilityDataDefinition(CapabilityDataDefinition other) {
+    public CapabilityDataDefinition(final CapabilityDataDefinition other) {
         this.setUniqueId(other.getUniqueId());
         this.setType(other.getType());
         this.setDescription(other.getDescription());
@@ -84,6 +90,7 @@ public class CapabilityDataDefinition extends ToscaDataDefinition {
 
         this.setSource(other.getSource());
         this.setOwnerType(other.getOwnerType());
+        this.setExternal(other.isExternal());
     }
 
     public CapabilityDataDefinition(CapabilityTypeDataDefinition other) {
@@ -422,11 +429,11 @@ public class CapabilityDataDefinition extends ToscaDataDefinition {
         String maxOccurrences = this.getMaxOccurrences();
         String source = this.getSource();
 
-
         return "CapabilityDefinition [uniqueId=" + uniqueId + ", description=" + description + ", name=" + name
-                + ", type=" + type + ", validSourceTypes=" + validSourceTypes + ", capabilitySources="
-                + capabilitySources + ", ownerId=" + ownerId + ", ownerName=" + ownerName
-                + ", minOccurrences=" + minOccurrences + ", maxOccurrences=" + maxOccurrences + ", path=" + path + ", source=" + source + "]";
+            + ", type=" + type + ", validSourceTypes=" + validSourceTypes + ", capabilitySources="
+            + capabilitySources + ", ownerId=" + ownerId + ", ownerName=" + ownerName
+            + ", minOccurrences=" + minOccurrences + ", maxOccurrences=" + maxOccurrences + ", path=" + path + ", source=" + source
+            + ", external=" + external + "]";
     }
 
     public enum OwnerType {
diff --git a/common-be/src/main/java/org/openecomp/sdc/be/exception/BusinessException.java b/common-be/src/main/java/org/openecomp/sdc/be/exception/BusinessException.java
new file mode 100644 (file)
index 0000000..69fb00f
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.openecomp.sdc.be.exception;
+
+public abstract class BusinessException extends RuntimeException {
+
+    protected BusinessException() {
+    }
+
+    protected BusinessException(final String message) {
+        super(message);
+    }
+
+    protected BusinessException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}
index bee2d3a..2ea7437 100644 (file)
@@ -67,11 +67,11 @@ public class CompositionRequirementsCapabilitiesTab extends AbstractPageObject {
     }
 
     private void loadRequirements() {
-        final List<WebElement> webElements = waitForAllElementsVisibility(By.xpath("//checkbox[@data-tests-id]"));
+        final List<WebElement> webElements = waitForAllElementsVisibility(By.xpath(XpathSelector.REQUIREMENT_EXTERNAL_CHECKBOX.getXPath()));
         checkboxExternalRequirementMap = new HashMap<>();
         webElements.forEach(webElement -> {
             final String dataTestsId = webElement.getAttribute("data-tests-id");
-            checkboxExternalRequirementMap.put(dataTestsId.substring("checkbox-mark-as-external-".length()), webElement);
+            checkboxExternalRequirementMap.put(dataTestsId.substring("checkbox-external-req-".length()), webElement);
         });
     }
 
@@ -94,7 +94,8 @@ public class CompositionRequirementsCapabilitiesTab extends AbstractPageObject {
     private enum XpathSelector {
         REQ_CAPABILITIES_TAB("//req-capabilities-tab"),
         CAPABILITIES_ACCORDION("//div[@data-tests-id='Capabilities-accordion']"),
-        REQUIREMENTS_ACCORDION("//div[@data-tests-id='Requirements-accordion']");
+        REQUIREMENTS_ACCORDION("//div[@data-tests-id='Requirements-accordion']"),
+        REQUIREMENT_EXTERNAL_CHECKBOX("//checkbox[starts-with(@data-tests-id, 'checkbox-external-req-')]");
 
         private final String xPath;