From 0278649141c9247b2ec56bb76998c3b9aa412874 Mon Sep 17 00:00:00 2001 From: JvD_Ericsson Date: Wed, 12 Jul 2023 17:50:10 +0100 Subject: Service import issues Issue-ID: SDC-4568 Signed-off-by: JvD_Ericsson Change-Id: Ife2c4cc78a16d0f93f3a6a24d5bfdd48263d43a5 Signed-off-by: Vasyl Razinkov --- .../csar/YamlTemplateParsingHandler.java | 37 +++++++++++++----- .../be/components/impl/ComponentBusinessLogic.java | 2 +- .../impl/ServiceImportBusinessLogic.java | 44 ++++++++++++++-------- .../csar/YamlTemplateParsingHandlerTest.java | 5 ++- .../components/impl/ResourceBusinessLogicTest.java | 2 +- .../impl/ServiceImportBusinessLogicTest.java | 6 ++- 6 files changed, 65 insertions(+), 31 deletions(-) diff --git a/catalog-be/src/main/java/org/openecomp/sdc/be/components/csar/YamlTemplateParsingHandler.java b/catalog-be/src/main/java/org/openecomp/sdc/be/components/csar/YamlTemplateParsingHandler.java index 7c031a29e7..fd2fa4bf1a 100644 --- a/catalog-be/src/main/java/org/openecomp/sdc/be/components/csar/YamlTemplateParsingHandler.java +++ b/catalog-be/src/main/java/org/openecomp/sdc/be/components/csar/YamlTemplateParsingHandler.java @@ -90,6 +90,7 @@ import org.openecomp.sdc.be.components.impl.GroupTypeBusinessLogic; import org.openecomp.sdc.be.components.impl.ImportUtils; import org.openecomp.sdc.be.components.impl.NodeFilterUploadCreator; import org.openecomp.sdc.be.components.impl.PolicyTypeBusinessLogic; +import org.openecomp.sdc.be.components.impl.ServiceBusinessLogic; import org.openecomp.sdc.be.components.impl.exceptions.ByActionStatusComponentException; import org.openecomp.sdc.be.components.utils.PropertiesUtils; import org.openecomp.sdc.be.config.BeEcompErrorManager; @@ -107,8 +108,10 @@ import org.openecomp.sdc.be.datatypes.elements.PropertyFilterConstraintDataDefin import org.openecomp.sdc.be.datatypes.elements.SubPropertyToscaFunction; import org.openecomp.sdc.be.datatypes.elements.SubstitutionFilterPropertyDataDefinition; import org.openecomp.sdc.be.datatypes.elements.ToscaFunction; +import org.openecomp.sdc.be.datatypes.elements.ToscaGetFunctionDataDefinition; import org.openecomp.sdc.be.datatypes.enums.ConstraintType; import org.openecomp.sdc.be.datatypes.elements.ToscaFunctionType; +import org.openecomp.sdc.be.datatypes.enums.ConstraintType; import org.openecomp.sdc.be.datatypes.enums.FilterValueType; import org.openecomp.sdc.be.datatypes.enums.PropertyFilterTargetType; import org.openecomp.sdc.be.model.CapabilityDefinition; @@ -123,6 +126,7 @@ import org.openecomp.sdc.be.model.ParsedToscaYamlInfo; import org.openecomp.sdc.be.model.PolicyDefinition; import org.openecomp.sdc.be.model.PolicyTypeDefinition; import org.openecomp.sdc.be.model.PropertyDefinition; +import org.openecomp.sdc.be.model.Resource; import org.openecomp.sdc.be.model.UploadArtifactInfo; import org.openecomp.sdc.be.model.UploadAttributeInfo; import org.openecomp.sdc.be.model.UploadCapInfo; @@ -154,18 +158,21 @@ public class YamlTemplateParsingHandler { private final GroupTypeBusinessLogic groupTypeBusinessLogic; private final AnnotationBusinessLogic annotationBusinessLogic; private final PolicyTypeBusinessLogic policyTypeBusinessLogic; + private final ServiceBusinessLogic serviceBusinessLogic; private final ToscaFunctionYamlParsingHandler toscaFunctionYamlParsingHandler; public YamlTemplateParsingHandler(JanusGraphDao janusGraphDao, GroupTypeBusinessLogic groupTypeBusinessLogic, AnnotationBusinessLogic annotationBusinessLogic, PolicyTypeBusinessLogic policyTypeBusinessLogic, + ServiceBusinessLogic serviceBusinessLogic, final ToscaFunctionYamlParsingHandler toscaFunctionYamlParsingHandler ) { this.janusGraphDao = janusGraphDao; this.groupTypeBusinessLogic = groupTypeBusinessLogic; this.annotationBusinessLogic = annotationBusinessLogic; this.policyTypeBusinessLogic = policyTypeBusinessLogic; + this.serviceBusinessLogic = serviceBusinessLogic; this.toscaFunctionYamlParsingHandler = toscaFunctionYamlParsingHandler; } @@ -192,9 +199,16 @@ public class YamlTemplateParsingHandler { parsedToscaYamlInfo.setPolicies(getPolicies(mappedToscaTemplate, component.getModel())); Map substitutionMappings = getSubstitutionMappings(mappedToscaTemplate); if (substitutionMappings != null) { - if (component.isService() && !interfaceTemplateYaml.isEmpty()) { - parsedToscaYamlInfo.setProperties(getProperties(loadYamlAsStrictMap(interfaceTemplateYaml))); - parsedToscaYamlInfo.setSubstitutionFilterProperties(getSubstitutionFilterProperties(mappedToscaTemplate)); + if (component.isService()) { + if (!interfaceTemplateYaml.isEmpty()) { + parsedToscaYamlInfo.setProperties(getProperties(loadYamlAsStrictMap(interfaceTemplateYaml))); + parsedToscaYamlInfo.setSubstitutionFilterProperties(getSubstitutionFilterProperties(mappedToscaTemplate)); + } else { + Resource resource = serviceBusinessLogic.fetchDerivedFromGenericType(component, null); + List properties = resource.getProperties(); + parsedToscaYamlInfo.setProperties(properties.stream().collect(Collectors.toMap(PropertyDefinition::getName, prop -> prop))); + parsedToscaYamlInfo.setSubstitutionFilterProperties(getSubstitutionFilterProperties(mappedToscaTemplate)); + } } if (substitutionMappings.get("properties") != null) { parsedToscaYamlInfo.setSubstitutionMappingProperties((Map>) substitutionMappings.get("properties")); @@ -930,7 +944,7 @@ public class YamlTemplateParsingHandler { .createNodeFilterData(nodeTemplateJsonMap.get(TypeUtils.ToscaTagNamesEnum.NODE_FILTER.getElementName()))); } } - + @SuppressWarnings("unchecked") private void setOccurrencesAndInstanceCount(UploadComponentInstanceInfo nodeTemplateInfo, Map nodeTemplateJsonMap) { if (nodeTemplateJsonMap.containsKey(TypeUtils.ToscaTagNamesEnum.OCCURRENCES.getElementName())) { @@ -1205,7 +1219,8 @@ public class YamlTemplateParsingHandler { if (toscaFunctionYamlParsingHandler.isPropertyValueToscaFunction(propValueObj)) { toscaFunctionYamlParsingHandler.buildToscaFunctionBasedOnPropertyValue(propValueMap).ifPresent(propertyDef::setToscaFunction); } else { - final Collection subPropertyToscaFunctions = buildSubPropertyToscaFunctions(propValueMap, new ArrayList<>()); + final Collection subPropertyToscaFunctions = + buildSubPropertyToscaFunctions(propValueMap, new ArrayList<>()); if (CollectionUtils.isNotEmpty(subPropertyToscaFunctions)) { Collection existingSubPropertyToscaFunctions = propertyDef.getSubPropertyToscaFunctions(); if (existingSubPropertyToscaFunctions == null) { @@ -1232,7 +1247,7 @@ public class YamlTemplateParsingHandler { } return propertyDef; } - + private Collection buildSubPropertyToscaFunctions(final Map propValueMap, final List path) { Collection subPropertyToscaFunctions = new ArrayList<>(); propValueMap.entrySet().stream().filter(entry -> entry.getValue() instanceof Map).forEach(entry -> { @@ -1240,7 +1255,7 @@ public class YamlTemplateParsingHandler { subPropertyPath.add(entry.getKey()); if (ToscaFunctionType.findType(((Map) entry.getValue()).keySet().iterator().next()).isPresent()) { Optional toscaFunction = - toscaFunctionYamlParsingHandler.buildToscaFunctionBasedOnPropertyValue((Map) entry.getValue()); + toscaFunctionYamlParsingHandler.buildToscaFunctionBasedOnPropertyValue((Map) entry.getValue()); if (toscaFunction.isPresent()) { SubPropertyToscaFunction subPropertyToscaFunction = new SubPropertyToscaFunction(); subPropertyToscaFunction.setToscaFunction(toscaFunction.get()); @@ -1310,7 +1325,8 @@ public class YamlTemplateParsingHandler { }.getType(); String stringValue = gson.toJson(value, type); if (toscaFunctionYamlParsingHandler.isPropertyValueToscaFunction(value)) { - toscaFunctionYamlParsingHandler.buildToscaFunctionBasedOnPropertyValue((Map) value).ifPresent(operationInput::setToscaFunction); + toscaFunctionYamlParsingHandler.buildToscaFunctionBasedOnPropertyValue((Map) value) + .ifPresent(operationInput::setToscaFunction); } else { final Collection subPropertyToscaFunctions = buildSubPropertyToscaFunctions(valueMap, new ArrayList<>()); if (CollectionUtils.isNotEmpty(subPropertyToscaFunctions)) { @@ -1420,8 +1436,9 @@ public class YamlTemplateParsingHandler { if (objValue instanceof Map) { Map objMap = (Map) objValue; Map propValueMap = new HashMap(); - propValueMap.put(String.valueOf(index),objValue); - final Collection subPropertyToscaFunctions = buildSubPropertyToscaFunctions(propValueMap, new ArrayList<>()); + propValueMap.put(String.valueOf(index), objValue); + final Collection subPropertyToscaFunctions = + buildSubPropertyToscaFunctions(propValueMap, new ArrayList<>()); if (CollectionUtils.isNotEmpty(subPropertyToscaFunctions)) { Collection existingSubPropertyToscaFunctions = propertyDef.getSubPropertyToscaFunctions(); if (existingSubPropertyToscaFunctions == null) { 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 28d105904b..b3d0bfc573 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 @@ -725,7 +725,7 @@ public abstract class ComponentBusinessLogic extends BaseBusinessLogic { return genericTypeResource; } - protected Resource fetchDerivedFromGenericType(final T component, final String toscaType) { + public 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()); diff --git a/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ServiceImportBusinessLogic.java b/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ServiceImportBusinessLogic.java index a31e9234c1..fff1ac5852 100644 --- a/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ServiceImportBusinessLogic.java +++ b/catalog-be/src/main/java/org/openecomp/sdc/be/components/impl/ServiceImportBusinessLogic.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.openecomp.sdc.be.components.impl; import static java.util.stream.Collectors.joining; @@ -202,16 +203,14 @@ public class ServiceImportBusinessLogic { private final IGraphLockOperation graphLockOperation; private final ToscaFunctionService toscaFunctionService; private final DataTypeBusinessLogic dataTypeBusinessLogic; - private ApplicationDataTypeCache applicationDataTypeCache; private final ArtifactTypeOperation artifactTypeOperation; - private final GroupTypeImportManager groupTypeImportManager; private final GroupTypeOperation groupTypeOperation; - private InterfaceLifecycleOperation interfaceLifecycleTypeOperation; - private InterfaceLifecycleTypeImportManager interfaceLifecycleTypeImportManager; - private final CapabilityTypeImportManager capabilityTypeImportManager; private final CapabilityTypeOperation capabilityTypeOperation; + private ApplicationDataTypeCache applicationDataTypeCache; + private InterfaceLifecycleOperation interfaceLifecycleTypeOperation; + private InterfaceLifecycleTypeImportManager interfaceLifecycleTypeImportManager; public ServiceImportBusinessLogic(final GroupBusinessLogic groupBusinessLogic, final ArtifactsBusinessLogic artifactsBusinessLogic, final ComponentsUtils componentsUtils, final ToscaOperationFacade toscaOperationFacade, @@ -692,7 +691,8 @@ public class ServiceImportBusinessLogic { Map inputs = parsedToscaYamlInfo.getInputs(); service = serviceImportParseLogic.createInputsOnService(service, inputs); log.trace("************* Finished to add inputs from yaml {}", yamlName); - ListDataDefinition substitutionFilterProperties = parsedToscaYamlInfo.getSubstitutionFilterProperties(); + ListDataDefinition substitutionFilterProperties = + parsedToscaYamlInfo.getSubstitutionFilterProperties(); service = serviceImportParseLogic.createSubstitutionFilterOnService(service, substitutionFilterProperties); log.trace("************* Added Substitution filter from interface yaml {}", yamlName); Map uploadComponentInstanceInfoMap = parsedToscaYamlInfo.getInstances(); @@ -768,13 +768,17 @@ public class ServiceImportBusinessLogic { final List componentInstances = component.getComponentInstances(); final String componentUniqueId = component.getUniqueId(); for (final InputDefinition input : inputs) { - if (isInputFromComponentInstanceProperty(input.getName(), componentInstances)) { + boolean isSubMapProp = false; + if (substitutionMappingProperties != null && !substitutionMappingProperties.isEmpty()) { + isSubMapProp = substitutionMappingProperties.entrySet().stream() + .anyMatch(stringEntry -> stringEntry.getValue().get(0).equals(input.getName())); + } + if (!isSubMapProp && isInputFromComponentInstanceProperty(input.getName(), componentInstances)) { associateInputToComponentInstanceProperty(userId, input, componentInstances, componentUniqueId); } else { associateInputToServiceProperty(userId, input, component, substitutionMappingProperties); } } - Either, StorageOperationStatus> either = toscaOperationFacade.updateInputsToComponent(inputs, componentUniqueId); if (either.isRight()) { throw new ComponentException(ActionStatus.GENERAL_ERROR); @@ -788,7 +792,8 @@ public class ServiceImportBusinessLogic { AtomicBoolean isInputFromCIProp = new AtomicBoolean(false); if (CollectionUtils.isNotEmpty(componentInstances)) { - outer: for (ComponentInstance instance : componentInstances) { + outer: + for (ComponentInstance instance : componentInstances) { for (PropertyDefinition instanceProperty : instance.getProperties()) { if (CollectionUtils.isNotEmpty(instanceProperty.getGetInputValues())) { for (GetInputValueDataDefinition getInputValueDataDefinition : instanceProperty.getGetInputValues()) { @@ -811,7 +816,8 @@ public class ServiceImportBusinessLogic { String componentInstanceId = null; ComponentInstanceProperty componentInstanceProperty = new ComponentInstanceProperty(); - outer: for (ComponentInstance instance : componentInstances) { + outer: + for (ComponentInstance instance : componentInstances) { for (PropertyDefinition instanceProperty : instance.getProperties()) { if (CollectionUtils.isNotEmpty(instanceProperty.getGetInputValues())) { for (GetInputValueDataDefinition getInputValueDataDefinition : instanceProperty.getGetInputValues()) { @@ -851,8 +857,9 @@ public class ServiceImportBusinessLogic { } }); - final Optional propDefOptional = properties.stream().filter(prop -> prop.getName().equals(propertyNameFromInput.get())) - .findFirst(); + final Optional propDefOptional = + properties.stream().filter(prop -> prop.getName().equals(propertyNameFromInput.get())) + .findFirst(); if (propDefOptional.isPresent()) { // From SELF final String componentUniqueId = component.getUniqueId(); @@ -868,6 +875,8 @@ public class ServiceImportBusinessLogic { if (either.isRight()) { throw new ComponentException(ActionStatus.GENERAL_ERROR); } + } else { + input.setMappedToComponentProperty(false); } } } @@ -897,8 +906,9 @@ public class ServiceImportBusinessLogic { boolean inTransaction, boolean shouldLock) { String nodeName = nodeTypeInfoToUpdateArtifacts.getNodeName(); Resource resource = preparedResource; - Map>> nodeTypesArtifactsToHandle = nodeTypeInfoToUpdateArtifacts - .getNodeTypesArtifactsToHandle(); + Map>> nodeTypesArtifactsToHandle = + nodeTypeInfoToUpdateArtifacts + .getNodeTypesArtifactsToHandle(); if (preparedResource.getResourceType() == ResourceTypeEnum.VF) { if (nodeName != null && nodeTypesArtifactsToHandle.get(nodeName) != null && !nodeTypesArtifactsToHandle.get(nodeName).isEmpty()) { Either, ResponseFormat> handleNodeTypeArtifactsRes = handleNodeTypeArtifacts(preparedResource, @@ -1025,7 +1035,8 @@ public class ServiceImportBusinessLogic { vfCsarArtifactsToHandle = new EnumMap<>(ArtifactsBusinessLogic.ArtifactOperationEnum.class); vfCsarArtifactsToHandle.put(artifactOperation.getArtifactOperationEnum(), artifactPathAndNameList.left().value()); } else { - Either>, ResponseFormat> findVfCsarArtifactsToHandleRes = findVfCsarArtifactsToHandle( + Either>, ResponseFormat> + findVfCsarArtifactsToHandleRes = findVfCsarArtifactsToHandle( component, artifactPathAndNameList.left().value(), csarInfo.getModifier()); if (findVfCsarArtifactsToHandleRes.isRight()) { resStatus = Either.right(findVfCsarArtifactsToHandleRes.right().value()); @@ -1316,7 +1327,8 @@ public class ServiceImportBusinessLogic { EnumMap> nodeTypeArtifactsToHandle = new EnumMap<>( ArtifactsBusinessLogic.ArtifactOperationEnum.class); Wrapper responseWrapper = new Wrapper<>(); - Either>, ResponseFormat> nodeTypeArtifactsToHandleRes = Either + Either>, ResponseFormat> + nodeTypeArtifactsToHandleRes = Either .left(nodeTypeArtifactsToHandle); try { List artifactsToUpload = new ArrayList<>(artifactPathAndNameList); diff --git a/catalog-be/src/test/java/org/openecomp/sdc/be/components/csar/YamlTemplateParsingHandlerTest.java b/catalog-be/src/test/java/org/openecomp/sdc/be/components/csar/YamlTemplateParsingHandlerTest.java index df3dd1f0d6..218e0267d2 100644 --- a/catalog-be/src/test/java/org/openecomp/sdc/be/components/csar/YamlTemplateParsingHandlerTest.java +++ b/catalog-be/src/test/java/org/openecomp/sdc/be/components/csar/YamlTemplateParsingHandlerTest.java @@ -34,6 +34,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.openecomp.sdc.be.components.impl.AnnotationBusinessLogic; import org.openecomp.sdc.be.components.impl.GroupTypeBusinessLogic; import org.openecomp.sdc.be.components.impl.PolicyTypeBusinessLogic; +import org.openecomp.sdc.be.components.impl.ServiceBusinessLogic; import org.openecomp.sdc.be.components.validation.AnnotationValidator; import org.openecomp.sdc.be.dao.janusgraph.JanusGraphDao; import org.openecomp.sdc.be.datatypes.elements.PropertyDataDefinition; @@ -91,6 +92,8 @@ public class YamlTemplateParsingHandlerTest { @Mock private PolicyTypeBusinessLogic policyTypeBusinessLogic; @Mock + private ServiceBusinessLogic serviceBusinessLogic; + @Mock private ToscaFunctionYamlParsingHandler toscaFunctionYamlParsingHandler; private YamlTemplateParsingHandler handler; @@ -139,7 +142,7 @@ public class YamlTemplateParsingHandlerTest { public void setup() { final var annotationBusinessLogic = new AnnotationBusinessLogic(annotationTypeOperations, annotationValidator); handler = new YamlTemplateParsingHandler(janusGraphDao, groupTypeBusinessLogic, annotationBusinessLogic, policyTypeBusinessLogic, - toscaFunctionYamlParsingHandler); + serviceBusinessLogic, toscaFunctionYamlParsingHandler); ReflectionTestUtils.setField(handler, "policyTypeBusinessLogic", policyTypeBusinessLogic); } diff --git a/catalog-be/src/test/java/org/openecomp/sdc/be/components/impl/ResourceBusinessLogicTest.java b/catalog-be/src/test/java/org/openecomp/sdc/be/components/impl/ResourceBusinessLogicTest.java index e9735710da..e4ebd78cce 100644 --- a/catalog-be/src/test/java/org/openecomp/sdc/be/components/impl/ResourceBusinessLogicTest.java +++ b/catalog-be/src/test/java/org/openecomp/sdc/be/components/impl/ResourceBusinessLogicTest.java @@ -1490,7 +1490,7 @@ class ResourceBusinessLogicTest { String resourceYml = new String(csar.get("Definitions/my_vnf.yaml")); YamlTemplateParsingHandler yamlTemplateParser = new YamlTemplateParsingHandler(mockJanusGraphDao, null, - Mockito.mock(AnnotationBusinessLogic.class), null, null); + Mockito.mock(AnnotationBusinessLogic.class), null, null, null); final ParsedToscaYamlInfo parsedToscaYamlInfo = yamlTemplateParser.parseResourceInfoFromYAML("Definitions/my_vnf.yml", resourceYml, Collections.EMPTY_MAP, Collections.EMPTY_MAP, "myVnf", resourceResponse, ""); diff --git a/catalog-be/src/test/java/org/openecomp/sdc/be/components/impl/ServiceImportBusinessLogicTest.java b/catalog-be/src/test/java/org/openecomp/sdc/be/components/impl/ServiceImportBusinessLogicTest.java index cf40778542..4b7e42b737 100644 --- a/catalog-be/src/test/java/org/openecomp/sdc/be/components/impl/ServiceImportBusinessLogicTest.java +++ b/catalog-be/src/test/java/org/openecomp/sdc/be/components/impl/ServiceImportBusinessLogicTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.anyList; import static org.mockito.Mockito.anyMap; import static org.mockito.Mockito.contains; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.isNull; import static org.mockito.Mockito.matches; @@ -211,8 +212,9 @@ class ServiceImportBusinessLogicTest extends ServiceImportBussinessLogicBaseTest when(csarBusinessLogic.getCsarInfo(any(Service.class), any(), any(User.class), any(Map.class), anyString())).thenReturn(csarInfo); when(serviceImportParseLogic.findNodeTypesArtifactsToHandle(any(Map.class), any(CsarInfo.class), any(Service.class))) .thenReturn(Either.left(new HashMap>>())); - when(csarBusinessLogic.getParsedToscaYamlInfo(anyString(), anyString(), any(), any(CsarInfo.class), any(), any(Service.class))) - .thenReturn(getParsedToscaYamlInfo()); + doReturn(getParsedToscaYamlInfo()).when(csarBusinessLogic).getParsedToscaYamlInfo(anyString(), anyString(), any(), any(CsarInfo.class), any(), any(Service.class)); +// when(csarBusinessLogic.getParsedToscaYamlInfo(anyString(), anyString(), any(), any(CsarInfo.class), any(), any(Service.class))) +// .thenReturn(getParsedToscaYamlInfo()); when(serviceBusinessLogic.lockComponentByName(newService.getSystemName(), oldService, CREATE_RESOURCE)).thenReturn(Either.left(true)); when(toscaOperationFacade.getLatestResourceByToscaResourceName(anyString())).thenReturn(Either.left(createOldResource())); when(serviceImportParseLogic.createServiceTransaction(oldService, csarInfo.getModifier(), false)).thenReturn(newService); -- cgit 1.2.3-korg