From 3dff1c221e58de6a81cf6bbdfb84fdf97665e484 Mon Sep 17 00:00:00 2001 From: JvD_Ericsson Date: Thu, 23 Mar 2023 11:52:27 +0000 Subject: Support to change substitution mapping node or version after service creation Before changing check for service property usage in sub mapping node change Issue-ID: SDC-4439 Issue-ID: SDC-4430 Signed-off-by: JvD_Ericsson Change-Id: Ia0f24c626ac836f0b4e7ffbe0004e7ab30089b25 --- .../files/default/error-configuration.yaml | 10 +- .../be/components/impl/ComponentBusinessLogic.java | 13 +- .../be/components/impl/ServiceBusinessLogic.java | 175 +++++++++++++++++++-- .../main/resources/config/error-configuration.yaml | 8 + 4 files changed, 192 insertions(+), 14 deletions(-) (limited to 'catalog-be/src/main') diff --git a/catalog-be/src/main/docker/backend/chef-repo/cookbooks/sdc-catalog-be/files/default/error-configuration.yaml b/catalog-be/src/main/docker/backend/chef-repo/cookbooks/sdc-catalog-be/files/default/error-configuration.yaml index ba2e58d6b8..05926151ba 100644 --- a/catalog-be/src/main/docker/backend/chef-repo/cookbooks/sdc-catalog-be/files/default/error-configuration.yaml +++ b/catalog-be/src/main/docker/backend/chef-repo/cookbooks/sdc-catalog-be/files/default/error-configuration.yaml @@ -2877,4 +2877,12 @@ errors: code: 400, message: "Input name '%1' already exist.", messageId: "SVC4016" - } \ No newline at end of file + } + + #---------SVC4017----------------------------- + # %1 - Map of component instance and properties + SUBSTITUTION_NODE_TYPE_PROPERTY_IN_USE: { + code: 409, + message: "Cannot change substitution node type as properties of the existing type are referenced by properties %1.", + messageId: "SVC4017" + } diff --git a/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ComponentBusinessLogic.java b/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ComponentBusinessLogic.java index 1b28435dd8..28d105904b 100644 --- a/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ComponentBusinessLogic.java +++ b/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ComponentBusinessLogic.java @@ -720,16 +720,21 @@ public abstract class ComponentBusinessLogic extends BaseBusinessLogic { } protected Resource fetchAndSetDerivedFromGenericType(final T component, final String toscaType) { + final Resource genericTypeResource = fetchDerivedFromGenericType(component, toscaType); + component.setDerivedFromGenericInfo(genericTypeResource); + return genericTypeResource; + } + + protected Resource fetchDerivedFromGenericType(final T component, final String toscaType) { final Either genericTypeEither = this.genericTypeBusinessLogic.fetchDerivedFromGenericType(component, toscaType); if (genericTypeEither.isRight()) { - log.debug("Failed to fetch latest generic type for component {} of type", component.getName(), component.assetType()); + log.debug("Failed to fetch latest generic type for component {} of type {}", component.getName(), component.assetType()); throw new ByActionStatusComponentException(ActionStatus.GENERIC_TYPE_NOT_FOUND, component.assetType()); } - final Resource genericTypeResource = genericTypeEither.left().value(); - component.setDerivedFromGenericInfo(genericTypeResource); - return genericTypeResource; + return genericTypeEither.left().value(); } + public Either>, ResponseFormat> getFilteredComponentInstanceProperties(String componentId, Map> filters, String userId) { diff --git a/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ServiceBusinessLogic.java b/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ServiceBusinessLogic.java index 9b095cc062..a9014b4b21 100644 --- a/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ServiceBusinessLogic.java +++ b/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ServiceBusinessLogic.java @@ -19,6 +19,7 @@ * Modifications copyright (c) 2019 Nokia * ================================================================================ */ + package org.openecomp.sdc.be.components.impl; import static org.apache.commons.collections.CollectionUtils.isNotEmpty; @@ -97,6 +98,10 @@ import org.openecomp.sdc.be.datatypes.elements.InterfaceDataDefinition; import org.openecomp.sdc.be.datatypes.elements.ListDataDefinition; import org.openecomp.sdc.be.datatypes.elements.OperationInputDefinition; import org.openecomp.sdc.be.datatypes.elements.OperationOutputDefinition; +import org.openecomp.sdc.be.datatypes.elements.PropertyDataDefinition; +import org.openecomp.sdc.be.datatypes.elements.ToscaFunction; +import org.openecomp.sdc.be.datatypes.elements.ToscaFunctionType; +import org.openecomp.sdc.be.datatypes.elements.ToscaGetFunctionDataDefinition; import org.openecomp.sdc.be.datatypes.enums.ComponentFieldsEnum; import org.openecomp.sdc.be.datatypes.enums.ComponentTypeEnum; import org.openecomp.sdc.be.datatypes.enums.JsonPresentationFields; @@ -189,7 +194,6 @@ public class ServiceBusinessLogic extends ComponentBusinessLogic { private final ServiceInstantiationTypeValidator serviceInstantiationTypeValidator; private final ServiceCategoryValidator serviceCategoryValidator; private final ServiceValidator serviceValidator; - private final PolicyBusinessLogic policyBusinessLogic; private final GroupBusinessLogic groupBusinessLogic; private ForwardingPathOperation forwardingPathOperation; private AuditCassandraDao auditCassandraDao; @@ -209,8 +213,7 @@ public class ServiceBusinessLogic extends ComponentBusinessLogic { ComponentDescriptionValidator componentDescriptionValidator, ModelOperation modelOperation, final ServiceRoleValidator serviceRoleValidator, final ServiceInstantiationTypeValidator serviceInstantiationTypeValidator, - final ServiceCategoryValidator serviceCategoryValidator, final ServiceValidator serviceValidator, - final PolicyBusinessLogic policyBusinessLogic) { + final ServiceCategoryValidator serviceCategoryValidator, final ServiceValidator serviceValidator) { super(elementDao, groupOperation, groupInstanceOperation, groupTypeOperation, groupBusinessLogic, interfaceOperation, interfaceLifecycleTypeOperation, artifactsBusinessLogic, artifactToscaOperation, componentContactIdValidator, componentNameValidator, componentTagsValidator, componentValidator, componentIconValidator, componentProjectCodeValidator, componentDescriptionValidator); @@ -224,7 +227,6 @@ public class ServiceBusinessLogic extends ComponentBusinessLogic { this.serviceInstantiationTypeValidator = serviceInstantiationTypeValidator; this.serviceCategoryValidator = serviceCategoryValidator; this.serviceValidator = serviceValidator; - this.policyBusinessLogic = policyBusinessLogic; this.groupBusinessLogic = groupBusinessLogic; } @@ -849,21 +851,42 @@ public class ServiceBusinessLogic extends ComponentBusinessLogic { log.info("Restricted operation for user: {}, on service: {}", user.getUserId(), currentService.getCreatorUserId()); return Either.right(componentsUtils.getResponseFormat(ActionStatus.RESTRICTED_OPERATION)); } - Either validationRsponse = validateAndUpdateServiceMetadata(user, currentService, serviceUpdate); - if (validationRsponse.isRight()) { + List subNodePropsToBeRemoved = getSubstitutionNodePropertiesToBeRemoved(currentService, serviceUpdate); + List subNodePropsToBeAdded = getSubstitutionNodePropertiesToBeAdded(currentService, serviceUpdate); + boolean subNodeChanged = isSubstitutionNodeChanged(currentService, serviceUpdate); + Either validationResponse = + validateAndUpdateServiceMetadata(user, currentService, serviceUpdate, subNodeChanged, ListUtils.emptyIfNull(subNodePropsToBeRemoved)); + if (validationResponse.isRight()) { log.info("service update metadata: validations field."); - return validationRsponse; + return validationResponse; } - Service serviceToUpdate = validationRsponse.left().value(); + Service serviceToUpdate = validationResponse.left().value(); // lock resource lockComponent(serviceId, currentService, "Update Service Metadata"); try { + if (subNodeChanged) { + if (!subNodePropsToBeRemoved.isEmpty()) { + removePropertiesFromService(currentService, subNodePropsToBeRemoved); + removeInputsFromService(currentService, subNodePropsToBeRemoved); + } + if (!subNodePropsToBeAdded.isEmpty()) { + addPropertiesToService(currentService, subNodePropsToBeAdded); + if (Constants.DEFAULT_MODEL_NAME.equals(currentService.getModel()) || currentService.getModel() == null) { + addInputsToService(currentService, subNodePropsToBeAdded); + } + } + } return toscaOperationFacade.updateToscaElement(serviceToUpdate).right().map(rf -> { janusGraphDao.rollback(); BeEcompErrorManager.getInstance().logBeSystemError("Update Service Metadata"); log.debug("failed to update sevice {}", serviceToUpdate.getUniqueId()); return (componentsUtils.getResponseFormat(ActionStatus.GENERAL_ERROR)); }).left().bind(this::updateCatalogAndCommit); + } catch (ComponentException e) { + janusGraphDao.rollback(); + BeEcompErrorManager.getInstance().logBeSystemError("Update Service Metadata"); + log.debug("failed to update sevice {}", serviceToUpdate.getUniqueId()); + return Either.right(componentsUtils.getResponseFormat(ActionStatus.GENERAL_ERROR)); } finally { graphLockOperation.unlockComponent(serviceId, NodeTypeEnum.Service); } @@ -1034,9 +1057,18 @@ public class ServiceBusinessLogic extends ComponentBusinessLogic { } @VisibleForTesting - Either validateAndUpdateServiceMetadata(User user, Service currentService, Service serviceUpdate) { + Either validateAndUpdateServiceMetadata(User user, Service currentService, Service serviceUpdate, boolean subNodeChanged, + List subNodePropsToBeRemoved) { try { boolean hasBeenCertified = ValidationUtils.hasBeenCertified(currentService.getVersion()); + if (subNodeChanged) { + if (!subNodePropsToBeRemoved.isEmpty()) { + areSubstitutionNodePropertiesInUse(currentService, subNodePropsToBeRemoved); + } + currentService.setDerivedFromGenericVersion(serviceUpdate.getDerivedFromGenericVersion()); + currentService.setDerivedFromGenericType(serviceUpdate.getDerivedFromGenericType()); + } + Either response = validateAndUpdateCategory(user, currentService, serviceUpdate, hasBeenCertified, UPDATE_SERVICE_METADATA); if (response.isRight()) { @@ -1103,6 +1135,131 @@ public class ServiceBusinessLogic extends ComponentBusinessLogic { } } + private void addPropertiesToService(Service currentService, List subNodePropsToBeAdded) { + ListUtils.emptyIfNull(subNodePropsToBeAdded).forEach(prop -> { + Either addPropertyEither = + toscaOperationFacade.addPropertyToComponent(prop, currentService); + if (addPropertyEither.isRight()) { + throw new ByActionStatusComponentException(ActionStatus.GENERAL_ERROR); + } + }); + } + + private void addInputsToService(Service currentService, List subNodePropsToBeAdded) { + ListUtils.emptyIfNull(subNodePropsToBeAdded).forEach(prop -> { + InputDefinition inputDef = new InputDefinition(prop); + Either status = + toscaOperationFacade.addInputToComponent(prop.getName(), inputDef, currentService); + if (status.isRight()) { + throw new ByActionStatusComponentException(ActionStatus.GENERAL_ERROR); + } + }); + } + + private void removePropertiesFromService(Service currentService, List subNodePropsToBeRemoved) { + List props = currentService.getProperties(); + List propsUniqueIdsToBeRemoved = + props.stream().filter(prop -> subNodePropsToBeRemoved.contains(prop.getName())).map(PropertyDefinition::getUniqueId) + .collect(Collectors.toList()); + ListUtils.emptyIfNull(props).stream().filter(prop -> propsUniqueIdsToBeRemoved.contains(prop.getUniqueId())).forEach(prop -> { + StorageOperationStatus status = toscaOperationFacade.deletePropertyOfComponent(currentService, prop.getName()); + if (status != StorageOperationStatus.OK) { + throw new ByActionStatusComponentException(ActionStatus.GENERAL_ERROR); + } + }); + } + + private void removeInputsFromService(Service currentService, List subNodePropsToBeRemoved) { + List props = currentService.getProperties(); + List inputs = currentService.getInputs(); + List propsUniqueIdsToBeRemoved = + props.stream().filter(prop -> subNodePropsToBeRemoved.contains(prop.getName())).map(PropertyDefinition::getUniqueId) + .collect(Collectors.toList()); + ListUtils.emptyIfNull(inputs).stream().filter(input -> input.isMappedToComponentProperty() && + (propsUniqueIdsToBeRemoved.contains(input.getPropertyId()) || subNodePropsToBeRemoved.contains(input.getName()))).forEach(input -> { + StorageOperationStatus status = toscaOperationFacade.deleteInputOfResource(currentService, input.getName()); + if (status != StorageOperationStatus.OK) { + throw new ByActionStatusComponentException(ActionStatus.GENERAL_ERROR); + } + }); + } + + private void areSubstitutionNodePropertiesInUse(Service service, List subNodePropsToBeRemoved) { + Map> componentInstancesProps = service.getComponentInstancesProperties(); + List propsUniqueIdsToBeRemoved = + ListUtils.emptyIfNull(service.getProperties()).stream().filter(prop -> subNodePropsToBeRemoved.contains(prop.getName())) + .map(PropertyDefinition::getUniqueId) + .collect(Collectors.toList()); + List inputsUniqueIdsToBeRemoved = ListUtils.emptyIfNull(service.getInputs()).stream() + .filter(input -> propsUniqueIdsToBeRemoved.contains(input.getPropertyId()) || subNodePropsToBeRemoved.contains(input.getName())) + .map(PropertyDefinition::getUniqueId) + .collect(Collectors.toList()); + Map> inUse = new HashMap<>(); + if (componentInstancesProps != null && !componentInstancesProps.isEmpty()) { + componentInstancesProps.forEach((compInstanceId, listOfProps) -> { + List propsInUse = new ArrayList<>(); + listOfProps.stream() + .filter(PropertyDataDefinition::isToscaFunction) + .filter(compProp -> ToscaFunctionType.isGetFunction(compProp.getToscaFunction().getType())) + .forEach(compProp -> { + ToscaFunction toscaFunction = compProp.getToscaFunction(); + ToscaGetFunctionDataDefinition toscaGetFunction = (ToscaGetFunctionDataDefinition) toscaFunction; + String propName = toscaGetFunction.getPropertyName(); + String propUniqueId = toscaGetFunction.getPropertyUniqueId(); + if (inputsUniqueIdsToBeRemoved.contains(propUniqueId) || propsUniqueIdsToBeRemoved.contains(propUniqueId) || + subNodePropsToBeRemoved.contains(propName)) { + propsInUse.add(compProp.getName()); + } + }); + if (!propsInUse.isEmpty()) { + Optional componentInstance = service.getComponentInstanceById(compInstanceId); + componentInstance.ifPresent(instance -> inUse.put(instance.getName(), propsInUse)); + } + + }); + } + if (!inUse.isEmpty()) { + String propsInUse = inUse.entrySet().stream().map(entry -> { + String properties = entry.getValue().stream().map(Object::toString).collect(Collectors.joining(", ")); + return properties + " on " + entry.getKey(); + }).collect(Collectors.joining(", properties ")); + throw new ByActionStatusComponentException(ActionStatus.SUBSTITUTION_NODE_TYPE_PROPERTY_IN_USE, propsInUse); + } + } + + + private boolean isSubstitutionNodeChanged(Service currentService, Service updatedService) { + String currentServiceType = currentService.getDerivedFromGenericType(); + String updatedServiceType = updatedService.getDerivedFromGenericType(); + String currentServiceVersion = currentService.getDerivedFromGenericVersion(); + String updatedServiceVersion = updatedService.getDerivedFromGenericVersion(); + return !(StringUtils.equals(currentServiceType, updatedServiceType) && StringUtils.equals(currentServiceVersion, updatedServiceVersion)); + } + + private List getSubstitutionNodePropertiesToBeRemoved(Service currentService, Service serviceUpdate) { + List currentProps = ListUtils.emptyIfNull(fetchDerivedFromGenericType(currentService, null).getProperties()); + List updatedProps = ListUtils.emptyIfNull(fetchDerivedFromGenericType(serviceUpdate, null).getProperties()); + if (!StringUtils.equals(currentService.getDerivedFromGenericType(), serviceUpdate.getDerivedFromGenericType())) { + return currentProps.stream().map(PropertyDefinition::getName).collect(Collectors.toList()); + } + List updatedPropNames = updatedProps.stream().map(PropertyDefinition::getName).collect(Collectors.toList()); + List propNamesToBeRemoved = currentProps.stream().map(PropertyDefinition::getName).collect(Collectors.toList()); + propNamesToBeRemoved.removeIf(updatedPropNames::contains); + return propNamesToBeRemoved; + } + + private List getSubstitutionNodePropertiesToBeAdded(Service currentService, Service serviceUpdate) { + List currentProps = ListUtils.emptyIfNull(fetchDerivedFromGenericType(currentService, null).getProperties()); + List updatedProps = ListUtils.emptyIfNull(fetchDerivedFromGenericType(serviceUpdate, null).getProperties()); + if (!StringUtils.equals(currentService.getDerivedFromGenericType(), serviceUpdate.getDerivedFromGenericType())) { + return updatedProps; + } + Set currentPropNames = currentProps.stream().map(PropertyDefinition::getName).collect(Collectors.toSet()); + updatedProps.removeIf(prop -> currentPropNames.contains(prop.getName())); + return updatedProps; + } + + private void verifyValuesAreIdentical(Object updatedValue, Object originalValue, String fieldName) { if (updatedValue != null && !updatedValue.equals(originalValue)) { log.info("update service: received request to update {} to {} the field is not updatable ignoring.", fieldName, updatedValue); diff --git a/catalog-be/src/main/resources/config/error-configuration.yaml b/catalog-be/src/main/resources/config/error-configuration.yaml index 56dd54c5e4..a4b56d530c 100644 --- a/catalog-be/src/main/resources/config/error-configuration.yaml +++ b/catalog-be/src/main/resources/config/error-configuration.yaml @@ -2869,4 +2869,12 @@ errors: code: 400, message: "Cannot change this properties constraints as the resource is an instance.", messageId: "SVC4015" + } + + #---------SVC4017----------------------------- + # %1 - Map of component instance and properties + SUBSTITUTION_NODE_TYPE_PROPERTY_IN_USE: { + code: 409, + message: "Cannot change substitution node type as properties of the existing type are referenced by properties %1.", + messageId: "SVC4017" } \ No newline at end of file -- cgit 1.2.3-korg