From 276117aaa383b16a0b62e2666a612e669b8f27c8 Mon Sep 17 00:00:00 2001 From: MichaelMorris Date: Thu, 12 Aug 2021 09:14:21 +0100 Subject: Support models extending models Signed-off-by: MichaelMorris Issue-ID: SDC-3668 Change-Id: Iad4d2a28c1c982e55e8835d4f30a9a212aefb6be --- .../sdc/be/servlets/ArchiveEndpointTest.java | 2 +- .../sdc/be/servlets/ModelServletTest.java | 15 ++++++ .../be/dao/janusgraph/JanusGraphGenericDao.java | 18 +++++++- .../java/org/openecomp/sdc/be/model/Model.java | 5 ++ .../be/model/operations/impl/ModelOperation.java | 53 ++++++++++++++++++++-- .../sdc/be/ui/model/ModelCreateRequest.java | 5 +- .../model/operations/impl/ModelOperationTest.java | 30 ++++++++++++ 7 files changed, 122 insertions(+), 6 deletions(-) diff --git a/catalog-be/src/test/java/org/openecomp/sdc/be/servlets/ArchiveEndpointTest.java b/catalog-be/src/test/java/org/openecomp/sdc/be/servlets/ArchiveEndpointTest.java index faede3d9c5..8a15f473c9 100644 --- a/catalog-be/src/test/java/org/openecomp/sdc/be/servlets/ArchiveEndpointTest.java +++ b/catalog-be/src/test/java/org/openecomp/sdc/be/servlets/ArchiveEndpointTest.java @@ -264,7 +264,7 @@ class ArchiveEndpointTest extends JerseyTest { @Bean ModelOperation modelOperation() { - return new ModelOperation(null, null, null); + return new ModelOperation(null, null, null, null); } private void initGraphForTest() { diff --git a/catalog-be/src/test/java/org/openecomp/sdc/be/servlets/ModelServletTest.java b/catalog-be/src/test/java/org/openecomp/sdc/be/servlets/ModelServletTest.java index e40124fff4..4e1c0e7945 100644 --- a/catalog-be/src/test/java/org/openecomp/sdc/be/servlets/ModelServletTest.java +++ b/catalog-be/src/test/java/org/openecomp/sdc/be/servlets/ModelServletTest.java @@ -199,6 +199,21 @@ class ModelServletTest extends JerseyTest { .post(Entity.entity(formDataMultiPart, MediaType.MULTIPART_FORM_DATA)); assertEquals(Status.OK.getStatusCode(), response.getStatus()); } + + @Test + void createModelWithDerivedFromSuccessTest() throws JsonProcessingException { + when(responseFormat.getStatus()).thenReturn(HttpStatus.OK_200); + when(componentsUtils.getResponseFormat(ActionStatus.CREATED)).thenReturn(responseFormat); + when(modelBusinessLogic.createModel(any(Model.class))).thenReturn(model); + ModelCreateRequest derviedModelCreateRequest = new ModelCreateRequest(); + derviedModelCreateRequest.setName("derivedModel"); + derviedModelCreateRequest.setDerivedFrom(model.getName()); + final FormDataMultiPart formDataMultiPart = buildCreateFormDataMultiPart(new byte[0], parseToJsonString(derviedModelCreateRequest)); + final var response = target(rootPath.toString()).request(MediaType.APPLICATION_JSON) + .header(Constants.USER_ID_HEADER, USER_ID) + .post(Entity.entity(formDataMultiPart, MediaType.MULTIPART_FORM_DATA)); + assertEquals(Status.OK.getStatusCode(), response.getStatus()); + } @Test void createModelFailTest() throws JsonProcessingException { diff --git a/catalog-dao/src/main/java/org/openecomp/sdc/be/dao/janusgraph/JanusGraphGenericDao.java b/catalog-dao/src/main/java/org/openecomp/sdc/be/dao/janusgraph/JanusGraphGenericDao.java index b2492cdb1a..2116dcc27e 100644 --- a/catalog-dao/src/main/java/org/openecomp/sdc/be/dao/janusgraph/JanusGraphGenericDao.java +++ b/catalog-dao/src/main/java/org/openecomp/sdc/be/dao/janusgraph/JanusGraphGenericDao.java @@ -693,7 +693,23 @@ public class JanusGraphGenericDao { if (modelVertices.isLeft()) { for (ImmutablePair vertexPair : modelVertices.left().value()) { - if (model.equals((String)vertexPair.getLeft().property("name").value())) { + if (modelVertexMatchesModel(vertexPair.getLeft(), model)) { + return true; + } + } + } + return false; + } + + private boolean modelVertexMatchesModel(final JanusGraphVertex modelVertex, final String model) { + if (model.equals((String)modelVertex.property("name").value())) { + return true; + } + final Either>, JanusGraphOperationStatus> derivedModels = + getParentVerticies(modelVertex, GraphEdgeLabels.DERIVED_FROM); + if (derivedModels.isLeft()) { + for (final ImmutablePair derivedModel : derivedModels.left().value()) { + if (modelVertexMatchesModel(derivedModel.left, model)) { return true; } } diff --git a/catalog-model/src/main/java/org/openecomp/sdc/be/model/Model.java b/catalog-model/src/main/java/org/openecomp/sdc/be/model/Model.java index 99d0f6599e..9c07c0565c 100644 --- a/catalog-model/src/main/java/org/openecomp/sdc/be/model/Model.java +++ b/catalog-model/src/main/java/org/openecomp/sdc/be/model/Model.java @@ -30,5 +30,10 @@ import lombok.NoArgsConstructor; public class Model { private String name; + private String derivedFrom; + + public Model(final String name) { + this.name = name; + } } diff --git a/catalog-model/src/main/java/org/openecomp/sdc/be/model/operations/impl/ModelOperation.java b/catalog-model/src/main/java/org/openecomp/sdc/be/model/operations/impl/ModelOperation.java index ddc0367a44..d4bd7996f2 100644 --- a/catalog-model/src/main/java/org/openecomp/sdc/be/model/operations/impl/ModelOperation.java +++ b/catalog-model/src/main/java/org/openecomp/sdc/be/model/operations/impl/ModelOperation.java @@ -19,6 +19,7 @@ package org.openecomp.sdc.be.model.operations.impl; import fj.data.Either; + import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.EnumMap; @@ -29,17 +30,24 @@ import java.util.Optional; import java.util.stream.Collectors; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; import org.openecomp.sdc.be.dao.api.ActionStatus; import org.openecomp.sdc.be.dao.cassandra.ToscaModelImportCassandraDao; +import org.openecomp.sdc.be.dao.graph.datatype.GraphEdge; +import org.openecomp.sdc.be.dao.graph.datatype.GraphRelation; import org.openecomp.sdc.be.dao.janusgraph.JanusGraphGenericDao; 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.VertexTypeEnum; +import org.openecomp.sdc.be.dao.neo4j.GraphEdgeLabels; import org.openecomp.sdc.be.data.model.ToscaImportByModel; import org.openecomp.sdc.be.datatypes.enums.GraphPropertyEnum; +import org.openecomp.sdc.be.datatypes.enums.NodeTypeEnum; import org.openecomp.sdc.be.model.Model; import org.openecomp.sdc.be.model.jsonjanusgraph.operations.exception.OperationException; +import org.openecomp.sdc.be.model.operations.api.DerivedFromOperation; +import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus; import org.openecomp.sdc.be.model.jsonjanusgraph.operations.exception.ModelOperationExceptionSupplier; import org.openecomp.sdc.be.resources.data.ModelData; import org.openecomp.sdc.common.log.enums.EcompLoggerErrorCode; @@ -55,14 +63,17 @@ public class ModelOperation { private final JanusGraphGenericDao janusGraphGenericDao; private final JanusGraphDao janusGraphDao; private final ToscaModelImportCassandraDao toscaModelImportCassandraDao; + private final DerivedFromOperation derivedFromOperation; @Autowired public ModelOperation(final JanusGraphGenericDao janusGraphGenericDao, final JanusGraphDao janusGraphDao, - final ToscaModelImportCassandraDao toscaModelImportCassandraDao) { + final ToscaModelImportCassandraDao toscaModelImportCassandraDao, + final DerivedFromOperation derivedFromOperation) { this.janusGraphGenericDao = janusGraphGenericDao; this.janusGraphDao = janusGraphDao; this.toscaModelImportCassandraDao = toscaModelImportCassandraDao; + this.derivedFromOperation = derivedFromOperation; } public Model createModel(final Model model, final boolean inTransaction) { @@ -80,7 +91,8 @@ public class ModelOperation { throw new OperationException(ActionStatus.GENERAL_ERROR, String.format("Failed to create model %s on JanusGraph with %s error", model, janusGraphOperationStatus)); } - result = new Model(createNode.left().value().getName()); + addDerivedFromRelation(model); + result = new Model(createNode.left().value().getName(), model.getDerivedFrom()); return result; } finally { if (!inTransaction) { @@ -92,6 +104,24 @@ public class ModelOperation { } } } + + private void addDerivedFromRelation(final Model model) { + final String derivedFrom = model.getDerivedFrom(); + if (derivedFrom == null) { + return; + } + log.debug("Adding derived from relation between model {} to its parent {}", + model.getName(), derivedFrom); + final Optional derivedFromModelOptional = this.findModelByName(derivedFrom); + if (derivedFromModelOptional.isPresent()) { + final Either result = derivedFromOperation.addDerivedFromRelation(UniqueIdBuilder.buildModelUid(model.getName()), + UniqueIdBuilder.buildModelUid(derivedFromModelOptional.get().getName()), NodeTypeEnum.Model); + if(result.isRight()) { + throw new OperationException(ActionStatus.GENERAL_ERROR, + String.format("Failed to create relationship from model % to derived from model %s on JanusGraph with %s error", model, derivedFrom, result.right().value())); + } + } + } public Optional findModelVertexByName(final String name) { if (StringUtils.isEmpty(name)) { @@ -171,7 +201,24 @@ public class ModelOperation { } private Model convertToModel(final GraphVertex modelGraphVertex) { - return new Model((String) modelGraphVertex.getMetadataProperty(GraphPropertyEnum.NAME)); + final String modelName = (String) modelGraphVertex.getMetadataProperty(GraphPropertyEnum.NAME); + + final Either, JanusGraphOperationStatus> parentNode = + janusGraphGenericDao.getChild(UniqueIdBuilder.getKeyByNodeType(NodeTypeEnum.Model), UniqueIdBuilder.buildModelUid(modelName), + GraphEdgeLabels.DERIVED_FROM, NodeTypeEnum.Model, ModelData.class); + log.debug("After retrieving DERIVED_FROM node of {}. status is {}", modelName, parentNode); + if (parentNode.isRight()) { + final JanusGraphOperationStatus janusGraphOperationStatus = parentNode.right().value(); + if (janusGraphOperationStatus != JanusGraphOperationStatus.NOT_FOUND) { + final var operationException = ModelOperationExceptionSupplier.failedToRetrieveModels(janusGraphOperationStatus).get(); + log.error(EcompLoggerErrorCode.DATA_ERROR, this.getClass().getName(), operationException.getMessage()); + throw operationException; + } + return new Model((String) modelGraphVertex.getMetadataProperty(GraphPropertyEnum.NAME)); + } else { + final ModelData parentModel = parentNode.left().value().getKey(); + return new Model((String) modelGraphVertex.getMetadataProperty(GraphPropertyEnum.NAME), parentModel.getName()); + } } } diff --git a/catalog-model/src/main/java/org/openecomp/sdc/be/ui/model/ModelCreateRequest.java b/catalog-model/src/main/java/org/openecomp/sdc/be/ui/model/ModelCreateRequest.java index 0d294b42c4..685d95e4ab 100644 --- a/catalog-model/src/main/java/org/openecomp/sdc/be/ui/model/ModelCreateRequest.java +++ b/catalog-model/src/main/java/org/openecomp/sdc/be/ui/model/ModelCreateRequest.java @@ -20,17 +20,20 @@ package org.openecomp.sdc.be.ui.model; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; /** * This class is responsible for holding all required fields from the create Model post request. * It also validates the Model 'name' field. */ -@Data +@Data @AllArgsConstructor @NoArgsConstructor public class ModelCreateRequest { @NotNull(message = "Model name cannot be null") @Size(min = 2, message = "Model name cannot be empty") private String name; + private String derivedFrom; } diff --git a/catalog-model/src/test/java/org/openecomp/sdc/be/model/operations/impl/ModelOperationTest.java b/catalog-model/src/test/java/org/openecomp/sdc/be/model/operations/impl/ModelOperationTest.java index c48a1524b8..e8d01dbb0f 100644 --- a/catalog-model/src/test/java/org/openecomp/sdc/be/model/operations/impl/ModelOperationTest.java +++ b/catalog-model/src/test/java/org/openecomp/sdc/be/model/operations/impl/ModelOperationTest.java @@ -25,6 +25,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -47,17 +49,21 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.openecomp.sdc.be.dao.api.ActionStatus; import org.openecomp.sdc.be.dao.cassandra.ToscaModelImportCassandraDao; +import org.openecomp.sdc.be.dao.graph.datatype.GraphRelation; import org.openecomp.sdc.be.dao.janusgraph.JanusGraphGenericDao; 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.VertexTypeEnum; +import org.openecomp.sdc.be.dao.neo4j.GraphEdgeLabels; import org.openecomp.sdc.be.data.model.ToscaImportByModel; import org.openecomp.sdc.be.datatypes.enums.GraphPropertyEnum; +import org.openecomp.sdc.be.datatypes.enums.NodeTypeEnum; import org.openecomp.sdc.be.model.Model; import org.openecomp.sdc.be.model.ModelTestBase; import org.openecomp.sdc.be.model.jsonjanusgraph.operations.exception.ModelOperationExceptionSupplier; import org.openecomp.sdc.be.model.jsonjanusgraph.operations.exception.OperationException; +import org.openecomp.sdc.be.model.operations.api.DerivedFromOperation; import org.openecomp.sdc.be.resources.data.ModelData; import org.springframework.test.context.ContextConfiguration; @@ -72,6 +78,8 @@ class ModelOperationTest extends ModelTestBase { private JanusGraphDao janusGraphDao; @Mock private ToscaModelImportCassandraDao toscaModelImportCassandraDao; + @Mock + private DerivedFromOperation derivedFromOperation; private final String modelName = "ETSI-SDC-MODEL-TEST"; @@ -93,6 +101,23 @@ class ModelOperationTest extends ModelTestBase { assertThat(createdModel).isNotNull(); assertThat(createdModel.getName()).isEqualTo(modelName); } + + @Test + void createDerivedModelSuccessTest() { + final String derivedModelName = "derivedModel"; + final ModelData modelData = new ModelData(derivedModelName, UniqueIdBuilder.buildModelUid(derivedModelName)); + when(janusGraphGenericDao.createNode(any(),any())).thenReturn(Either.left(modelData)); + + final GraphVertex modelVertex = new GraphVertex(); + modelVertex.addMetadataProperty(GraphPropertyEnum.NAME, "baseModel"); + when(janusGraphDao.getByCriteria(eq(VertexTypeEnum.MODEL), anyMap())).thenReturn(Either.left(Collections.singletonList(modelVertex))); + when(janusGraphGenericDao.getChild(eq("uid"), anyString(), eq(GraphEdgeLabels.DERIVED_FROM), eq(NodeTypeEnum.Model), eq(ModelData.class))).thenReturn(Either.right(JanusGraphOperationStatus.NOT_FOUND)); + when(derivedFromOperation.addDerivedFromRelation("model.derivedModel", "model.baseModel", NodeTypeEnum.Model)).thenReturn(Either.left(new GraphRelation())); + + final Model createdModel = modelOperation.createModel(new Model(derivedModelName, modelName), false); + assertThat(createdModel).isNotNull(); + assertThat(createdModel.getName()).isEqualTo(derivedModelName); + } @Test void createModelFailWithModelAlreadyExistTest() { @@ -200,6 +225,8 @@ class ModelOperationTest extends ModelTestBase { final GraphVertex expectedVertex = mock(GraphVertex.class); when(expectedVertex.getMetadataProperty(GraphPropertyEnum.NAME)).thenReturn(modelName); when(janusGraphDao.getByCriteria(eq(VertexTypeEnum.MODEL), mapArgumentCaptor.capture())).thenReturn(Either.left(List.of(expectedVertex))); + when(janusGraphGenericDao.getChild("uid", UniqueIdBuilder.buildModelUid(modelName), GraphEdgeLabels.DERIVED_FROM, NodeTypeEnum.Model, + ModelData.class)).thenReturn(Either.right(JanusGraphOperationStatus.NOT_FOUND)); final Optional modelByNameOpt = modelOperation.findModelByName(modelName); final Map value = mapArgumentCaptor.getValue(); @@ -232,6 +259,9 @@ class ModelOperationTest extends ModelTestBase { final GraphVertex expectedVertex = mock(GraphVertex.class); when(expectedVertex.getMetadataProperty(GraphPropertyEnum.NAME)).thenReturn(modelName); when(janusGraphDao.getByCriteria(VertexTypeEnum.MODEL, Collections.emptyMap())).thenReturn(Either.left(List.of(expectedVertex))); + when(janusGraphGenericDao.getChild("uid", UniqueIdBuilder.buildModelUid(modelName), GraphEdgeLabels.DERIVED_FROM, NodeTypeEnum.Model, + ModelData.class)).thenReturn(Either.right(JanusGraphOperationStatus.NOT_FOUND)); + final List actualModelList = modelOperation.findAllModels(); assertFalse(actualModelList.isEmpty()); assertEquals(1, actualModelList.size()); -- cgit 1.2.3-korg