From 5ec525d780ef43770a896cda70ce633af1503095 Mon Sep 17 00:00:00 2001 From: wr148d Date: Fri, 12 Jan 2024 10:46:55 -0500 Subject: [PATCH 01/16] [AAI] Promote Fiete Ostkamp and Nandkishor Patke to AAI Contributors Issue-ID: AAI-3729 Signed-off-by: wr148d Change-Id: I022964c0e538fe93686a43e0ed016bbf7399e832 --- INFO.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/INFO.yaml b/INFO.yaml index fb42332..3cd8e0a 100644 --- a/INFO.yaml +++ b/INFO.yaml @@ -41,6 +41,16 @@ committers: id: 'jimmydot' company: 'ATT' timezone: 'America/Detroit' + - name: 'Fiete Ostkamp' + email: 'fiete.ostkamp@telekom.de' + id: 'fostkamp' + company: 'Deutsche Telekom' + timezone: 'Europe/Berlin' + - name: 'Nandkishor Patke' + email: 'nandkishor-laxman.patke@t-systems.com' + id: 'nandkishorpatke' + company: 'T-Systems' + timezone: 'Asia/Kolkata' tsc: approval: 'https://lists.onap.org/g/onap-tsc' changes: @@ -65,3 +75,9 @@ tsc: - type: 'removal' name: 'Harish Kajur' link: 'https://lists.onap.org/g/onap-tsc/message/8850' + - type: 'Addition' + name: 'Fiete Ostkamp' + link: 'https://lists.onap.org/g/onap-tsc/message/9499' + - type: 'Addition' + name: 'Nandkishor Patke' + link: 'https://lists.onap.org/g/onap-tsc/message/9499' -- 2.16.6 From c7d3869e64534a06692191d0cdfecb2b30138c13 Mon Sep 17 00:00:00 2001 From: Fiete Ostkamp Date: Thu, 22 Feb 2024 13:19:35 +0100 Subject: [PATCH 02/16] Update aai-common-alpine base image in model-loader - update aai-common-alpine base image from 1.8.1 to 1.13.2 - rename README file Issue-ID: AAI-3785 Change-Id: I0343c5ffc4dc3b00fd95cdd1a18f4d9ca663f832 Signed-off-by: Fiete Ostkamp --- .gitignore | 1 + Readme.md => README.md | 0 pom.xml | 2 +- 3 files changed, 2 insertions(+), 1 deletion(-) rename Readme.md => README.md (100%) diff --git a/.gitignore b/.gitignore index 56f75e2..e4b4035 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ debug-logs/ .checkstyle .idea/ *.iml +.vscode \ No newline at end of file diff --git a/Readme.md b/README.md similarity index 100% rename from Readme.md rename to README.md diff --git a/pom.xml b/pom.xml index abf60e2..27978bb 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ ${project.build.directory}/${project.artifactId}-${project.version}-build/ onap alpine - 1.8.1 + 1.13.2 yyyyMMdd'T'HHmmss'Z' -- 2.16.6 From b5c15a4baafeac466c1565f2237d6f8cdaf9fd72 Mon Sep 17 00:00:00 2001 From: Fiete Ostkamp Date: Wed, 28 Feb 2024 14:39:51 +0100 Subject: [PATCH 03/16] Update model-loader to spring-boot 2.4 - update spring boot from 2.3.12 to 2.4.13 release - migrate JUnit 4 to JUnit 5 - bump version to 1.13.5 Issue-ID: AAI-3788 Change-Id: I280dd09194dfb4c9810c01933fdd18ea7f3d03a9 Signed-off-by: Fiete Ostkamp --- pom.xml | 28 +++++++++------ src/main/resources/application.properties | 4 +-- .../modelloader/TestModelLoaderApplication.java | 4 +-- .../modelloader/config/TestModelLoaderConfig.java | 10 +++--- .../csar/extractor/VnfCatalogExtractorTest.java | 8 ++--- .../catalog/TestVnfCatalogArtifactHandler.java | 4 +-- .../entity/catalog/TestVnfImageException.java | 2 +- .../modelloader/entity/model/ModelSorterTest.java | 15 ++++---- .../entity/model/TestModelArtifactHandler.java | 8 ++--- .../entity/model/TestModelArtifactParser.java | 2 +- .../entity/model/TestNamedQueryArtifactParser.java | 8 ++--- .../extraction/TestArtifactInfoExtractor.java | 28 +++++++-------- .../ArtifactDownloadManagerVnfcTest.java | 14 ++++---- .../TestArtifactDeploymentManager.java | 10 +++--- .../notification/TestArtifactDownloadManager.java | 10 +++--- .../notification/TestBabelArtifactConverter.java | 42 ++++++++++++---------- .../notification/TestEventCallback.java | 10 +++--- .../notification/TestNotificationDataImpl.java | 2 +- .../notification/TestNotificationPublisher.java | 12 +++---- .../modelloader/restclient/TestAaiRestClient.java | 2 +- .../restclient/TestAaiServiceClient.java | 12 +++---- .../restclient/TestBabelServiceClient.java | 10 +++--- .../modelloader/service/TestArtifactInfoImpl.java | 2 +- .../service/TestModelLoaderService.java | 11 +++--- .../service/TestModelLoaderServiceWithSdc.java | 9 ++--- .../aai/modelloader/util/TestGizmoTranslator.java | 9 +++-- .../aai/modelloader/util/TestJsonXmlConverter.java | 2 +- version.properties | 4 +-- 28 files changed, 148 insertions(+), 134 deletions(-) diff --git a/pom.xml b/pom.xml index 27978bb..a311908 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ org.onap.aai.model-loader model-loader aai-model-loader - 1.12.0-SNAPSHOT + 1.13.5-SNAPSHOT @@ -51,7 +51,7 @@ org.onap.aai.modelloader.service.ModelLoaderApplication https://nexus.onap.org ${basedir}/target - 2.3.12.RELEASE + 2.4.13 1.10.0 1.22 1.3 @@ -278,12 +278,14 @@ ch.qos.logback logback-classic - ${logback.version} + ${logback.version} + ch.qos.logback logback-core - ${logback.version} + ${logback.version} + org.onap.sdc.sdc-distribution-client @@ -293,7 +295,8 @@ org.json json - 20131018 + 20131018 + org.eclipse.jetty @@ -321,17 +324,22 @@ - - junit - junit - test - org.hamcrest hamcrest-all ${hamcrest-all.version} test + + org.junit.jupiter + junit-jupiter + test + + + org.junit.jupiter + junit-jupiter-api + test + org.mockito mockito-core diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 4ea6f0a..7273277 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -12,9 +12,9 @@ server.port=9500 #server.ssl.keyStoreType= #server.ssl.keyAlias= -server.tomcat.max-threads=200 +server.tomcat.threads.max=200 # The minimum number of threads always kept alive -server.tomcat.min-spare-threads=25 +server.tomcat.threads.min-spare=25 # Spring Boot logging logging.config=${logback.configurationFile} diff --git a/src/test/java/org/onap/aai/modelloader/TestModelLoaderApplication.java b/src/test/java/org/onap/aai/modelloader/TestModelLoaderApplication.java index 0387f2d..cd39066 100644 --- a/src/test/java/org/onap/aai/modelloader/TestModelLoaderApplication.java +++ b/src/test/java/org/onap/aai/modelloader/TestModelLoaderApplication.java @@ -20,9 +20,9 @@ */ package org.onap.aai.modelloader; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Tests for ModelLoaderApplication class. diff --git a/src/test/java/org/onap/aai/modelloader/config/TestModelLoaderConfig.java b/src/test/java/org/onap/aai/modelloader/config/TestModelLoaderConfig.java index e19b1e2..87fbd6a 100644 --- a/src/test/java/org/onap/aai/modelloader/config/TestModelLoaderConfig.java +++ b/src/test/java/org/onap/aai/modelloader/config/TestModelLoaderConfig.java @@ -20,8 +20,8 @@ */ package org.onap.aai.modelloader.config; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import java.io.File; import java.io.FileInputStream; @@ -29,7 +29,7 @@ import java.io.IOException; import java.util.List; import java.util.Properties; import org.eclipse.jetty.util.security.Password; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onap.aai.modelloader.restclient.AaiRestClient; import org.onap.sdc.utils.ArtifactTypeEnum; @@ -157,13 +157,13 @@ public class TestModelLoaderConfig { ModelLoaderConfig config = new ModelLoaderConfig(props, null); AaiRestClient aaiClient = new AaiRestClient(config); - assertFalse("Empty AAI Password should result in no basic authentication", aaiClient.useBasicAuth()); + assertFalse(aaiClient.useBasicAuth(), "Empty AAI Password should result in no basic authentication"); props.load(new FileInputStream("src/test/resources/model-loader-no-auth-password.properties")); config = new ModelLoaderConfig(props, null); aaiClient = new AaiRestClient(config); - assertFalse("No AAI Password should result in no basic authentication", aaiClient.useBasicAuth()); + assertFalse(aaiClient.useBasicAuth(), "No AAI Password should result in no basic authentication"); } @Test diff --git a/src/test/java/org/onap/aai/modelloader/csar/extractor/VnfCatalogExtractorTest.java b/src/test/java/org/onap/aai/modelloader/csar/extractor/VnfCatalogExtractorTest.java index 043edc3..b3335fc 100644 --- a/src/test/java/org/onap/aai/modelloader/csar/extractor/VnfCatalogExtractorTest.java +++ b/src/test/java/org/onap/aai/modelloader/csar/extractor/VnfCatalogExtractorTest.java @@ -22,8 +22,8 @@ package org.onap.aai.modelloader.csar.extractor; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; @@ -32,7 +32,7 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onap.aai.modelloader.entity.Artifact; import org.onap.aai.modelloader.entity.ArtifactType; import org.onap.aai.modelloader.extraction.InvalidArchiveException; @@ -86,8 +86,8 @@ public class VnfCatalogExtractorTest { List vnfcArtifacts = new VnfCatalogExtractor().extract( new ArtifactTestUtils().loadResource("compressedArtifacts/noVnfcFilesArchive.csar"), "noVnfcFilesArchive.csar"); - assertTrue("No VNFC files should have been extracted, but " + vnfcArtifacts.size() + " were found.", - vnfcArtifacts.isEmpty()); + assertTrue(vnfcArtifacts.isEmpty(), + "No VNFC files should have been extracted, but " + vnfcArtifacts.size() + " were found."); } @Test diff --git a/src/test/java/org/onap/aai/modelloader/entity/catalog/TestVnfCatalogArtifactHandler.java b/src/test/java/org/onap/aai/modelloader/entity/catalog/TestVnfCatalogArtifactHandler.java index d4a0020..8145599 100644 --- a/src/test/java/org/onap/aai/modelloader/entity/catalog/TestVnfCatalogArtifactHandler.java +++ b/src/test/java/org/onap/aai/modelloader/entity/catalog/TestVnfCatalogArtifactHandler.java @@ -23,7 +23,7 @@ package org.onap.aai.modelloader.entity.catalog; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -38,7 +38,7 @@ import java.util.Properties; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.onap.aai.modelloader.config.ModelLoaderConfig; diff --git a/src/test/java/org/onap/aai/modelloader/entity/catalog/TestVnfImageException.java b/src/test/java/org/onap/aai/modelloader/entity/catalog/TestVnfImageException.java index 88c1941..793ed9a 100644 --- a/src/test/java/org/onap/aai/modelloader/entity/catalog/TestVnfImageException.java +++ b/src/test/java/org/onap/aai/modelloader/entity/catalog/TestVnfImageException.java @@ -24,7 +24,7 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Tests for NotificationDataImpl class. diff --git a/src/test/java/org/onap/aai/modelloader/entity/model/ModelSorterTest.java b/src/test/java/org/onap/aai/modelloader/entity/model/ModelSorterTest.java index 86985d8..0c79dc5 100644 --- a/src/test/java/org/onap/aai/modelloader/entity/model/ModelSorterTest.java +++ b/src/test/java/org/onap/aai/modelloader/entity/model/ModelSorterTest.java @@ -26,13 +26,14 @@ import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.isEmptyString; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onap.aai.modelloader.entity.Artifact; public class ModelSorterTest { @@ -129,12 +130,14 @@ public class ModelSorterTest { assertThat(new ModelSorter().sort(Arrays.asList(nq1, nq2, artifact)), is(expected)); } - @Test(expected = BabelArtifactParsingException.class) + @Test public void circularDependency() throws BabelArtifactParsingException { - List modelList = new ArrayList(); - modelList.add(buildTestModel("aaaa", "1111", "bbbb|1111")); - modelList.add(buildTestModel("bbbb", "1111", "aaaa|1111")); - new ModelSorter().sort(modelList); + assertThrows(BabelArtifactParsingException.class, () -> { + List modelList = new ArrayList(); + modelList.add(buildTestModel("aaaa", "1111", "bbbb|1111")); + modelList.add(buildTestModel("bbbb", "1111", "aaaa|1111")); + new ModelSorter().sort(modelList); + }); } private ModelArtifact buildTestModel() { diff --git a/src/test/java/org/onap/aai/modelloader/entity/model/TestModelArtifactHandler.java b/src/test/java/org/onap/aai/modelloader/entity/model/TestModelArtifactHandler.java index 3418a6b..fac50cb 100644 --- a/src/test/java/org/onap/aai/modelloader/entity/model/TestModelArtifactHandler.java +++ b/src/test/java/org/onap/aai/modelloader/entity/model/TestModelArtifactHandler.java @@ -23,7 +23,7 @@ package org.onap.aai.modelloader.entity.model; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -34,8 +34,8 @@ import java.util.List; import javax.ws.rs.core.Response; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.onap.aai.modelloader.config.ModelLoaderConfig; @@ -55,7 +55,7 @@ public class TestModelArtifactHandler { @Mock private AaiRestClient aaiClient; - @Before + @BeforeEach public void setupMocks() { MockitoAnnotations.initMocks(this); } diff --git a/src/test/java/org/onap/aai/modelloader/entity/model/TestModelArtifactParser.java b/src/test/java/org/onap/aai/modelloader/entity/model/TestModelArtifactParser.java index 9b90aa2..fc0556f 100644 --- a/src/test/java/org/onap/aai/modelloader/entity/model/TestModelArtifactParser.java +++ b/src/test/java/org/onap/aai/modelloader/entity/model/TestModelArtifactParser.java @@ -30,7 +30,7 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onap.aai.modelloader.entity.Artifact; import org.springframework.util.CollectionUtils; diff --git a/src/test/java/org/onap/aai/modelloader/entity/model/TestNamedQueryArtifactParser.java b/src/test/java/org/onap/aai/modelloader/entity/model/TestNamedQueryArtifactParser.java index 8eebd06..9ea900f 100644 --- a/src/test/java/org/onap/aai/modelloader/entity/model/TestNamedQueryArtifactParser.java +++ b/src/test/java/org/onap/aai/modelloader/entity/model/TestNamedQueryArtifactParser.java @@ -23,15 +23,15 @@ package org.onap.aai.modelloader.entity.model; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.isEmptyString; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onap.aai.modelloader.entity.Artifact; public class TestNamedQueryArtifactParser { diff --git a/src/test/java/org/onap/aai/modelloader/extraction/TestArtifactInfoExtractor.java b/src/test/java/org/onap/aai/modelloader/extraction/TestArtifactInfoExtractor.java index 3bfb2a5..142b3c0 100644 --- a/src/test/java/org/onap/aai/modelloader/extraction/TestArtifactInfoExtractor.java +++ b/src/test/java/org/onap/aai/modelloader/extraction/TestArtifactInfoExtractor.java @@ -20,8 +20,8 @@ */ package org.onap.aai.modelloader.extraction; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.onap.aai.modelloader.fixture.NotificationDataFixtureBuilder.getEmptyNotificationData; import static org.onap.aai.modelloader.fixture.NotificationDataFixtureBuilder.getNotificationDataWithOneResource; import static org.onap.aai.modelloader.fixture.NotificationDataFixtureBuilder.getNotificationDataWithOneService; @@ -29,9 +29,9 @@ import static org.onap.aai.modelloader.fixture.NotificationDataFixtureBuilder.ge import java.util.ArrayList; import java.util.List; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onap.aai.modelloader.fixture.ArtifactInfoBuilder; import org.onap.aai.modelloader.fixture.MockNotificationDataImpl; import org.onap.sdc.api.notification.IArtifactInfo; @@ -44,12 +44,12 @@ public class TestArtifactInfoExtractor { private ArtifactInfoExtractor extractor; - @Before + @BeforeEach public void setup() { extractor = new ArtifactInfoExtractor(); } - @After + @AfterEach public void tearDown() { extractor = null; } @@ -60,7 +60,7 @@ public class TestArtifactInfoExtractor { } private void doEmptyArtifactsTest(INotificationData notificationData) { - assertTrue("The list returned should have been empty", extractor.extract(notificationData).isEmpty()); + assertTrue(extractor.extract(notificationData).isEmpty(), "The list returned should have been empty"); } @Test @@ -79,8 +79,8 @@ public class TestArtifactInfoExtractor { List artifacts = extractor.extract(getNotificationDataWithOneService()); - assertEquals("One artifact should have been returned", 1, artifacts.size()); - assertEquals("The actual artifact did not match the expected one", expected, artifacts.get(0)); + assertEquals(1, artifacts.size(), "One artifact should have been returned"); + assertEquals(expected, artifacts.get(0), "The actual artifact did not match the expected one"); } @Test @@ -90,8 +90,8 @@ public class TestArtifactInfoExtractor { List artifacts = extractor.extract(getNotificationDataWithOneResource()); - assertEquals("One artifact should have been returned", 1, artifacts.size()); - assertEquals("The actual artifact did not match the expected one", expectedArtifacts, artifacts); + assertEquals(1, artifacts.size(), "One artifact should have been returned"); + assertEquals(expectedArtifacts, artifacts, "The actual artifact did not match the expected one"); } @Test @@ -102,7 +102,7 @@ public class TestArtifactInfoExtractor { List artifacts = extractor.extract(getNotificationDataWithOneServiceAndResources()); - assertEquals("Two artifact should have been returned", 2, artifacts.size()); - assertEquals("The actual artifact did not match the expected one", expectedArtifacts, artifacts); + assertEquals(2, artifacts.size(), "Two artifact should have been returned"); + assertEquals(expectedArtifacts, artifacts, "The actual artifact did not match the expected one"); } } diff --git a/src/test/java/org/onap/aai/modelloader/notification/ArtifactDownloadManagerVnfcTest.java b/src/test/java/org/onap/aai/modelloader/notification/ArtifactDownloadManagerVnfcTest.java index 3710d62..1b8f33b 100644 --- a/src/test/java/org/onap/aai/modelloader/notification/ArtifactDownloadManagerVnfcTest.java +++ b/src/test/java/org/onap/aai/modelloader/notification/ArtifactDownloadManagerVnfcTest.java @@ -22,7 +22,7 @@ package org.onap.aai.modelloader.notification; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; @@ -34,8 +34,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Properties; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.onap.aai.babel.service.data.BabelArtifact; import org.onap.aai.modelloader.config.ModelLoaderConfig; @@ -71,7 +71,7 @@ public class ArtifactDownloadManagerVnfcTest { private BabelServiceClientFactory mockClientFactory; private VnfCatalogExtractor mockVnfCatalogExtractor; - @Before + @BeforeEach public void setup() throws Exception { mockBabelClient = mock(BabelServiceClient.class); mockDistributionClient = mock(IDistributionClient.class); @@ -107,7 +107,7 @@ public class ArtifactDownloadManagerVnfcTest { assertThat(downloadManager.downloadArtifacts(data, data.getServiceArtifacts(), modelArtifacts, catalogFiles), is(true)); - assertEquals("There should have been some catalog files", 2, catalogFiles.size()); + assertEquals(2, catalogFiles.size(), "There should have been some catalog files"); } @Test @@ -125,7 +125,7 @@ public class ArtifactDownloadManagerVnfcTest { assertThat(downloadManager.downloadArtifacts(data, data.getServiceArtifacts(), modelArtifacts, catalogFiles), is(true)); - assertEquals("There should have been some catalog files", 3, catalogFiles.size()); + assertEquals(3, catalogFiles.size(), "There should have been some catalog files"); } @Test @@ -143,7 +143,7 @@ public class ArtifactDownloadManagerVnfcTest { assertThat(downloadManager.downloadArtifacts(data, data.getServiceArtifacts(), modelArtifacts, catalogFiles), is(true)); - assertEquals("There should not have been any catalog files", 0, catalogFiles.size()); + assertEquals(0, catalogFiles.size(), "There should not have been any catalog files"); } @Test diff --git a/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDeploymentManager.java b/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDeploymentManager.java index a63710e..2cac3c9 100644 --- a/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDeploymentManager.java +++ b/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDeploymentManager.java @@ -34,9 +34,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Properties; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.onap.aai.babel.service.data.BabelArtifact; import org.onap.aai.modelloader.config.ModelLoaderConfig; @@ -66,7 +66,7 @@ public class TestArtifactDeploymentManager { private ModelArtifactHandler mockModelArtifactHandler; private VnfCatalogArtifactHandler mockVnfCatalogArtifactHandler; - @Before + @BeforeEach public void setup() throws IOException { configProperties = new Properties(); configProperties.load(this.getClass().getClassLoader().getResourceAsStream(CONFIG_FILE)); @@ -80,7 +80,7 @@ public class TestArtifactDeploymentManager { ReflectionTestUtils.setField(manager, "vnfCatalogArtifactHandler", mockVnfCatalogArtifactHandler); } - @After + @AfterEach public void tearDown() { configProperties = null; mockModelArtifactHandler = null; diff --git a/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDownloadManager.java b/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDownloadManager.java index 3d1cf15..561791b 100644 --- a/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDownloadManager.java +++ b/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDownloadManager.java @@ -41,9 +41,9 @@ import java.util.List; import java.util.Properties; import org.hamcrest.collection.IsEmptyCollection; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.onap.aai.babel.service.data.BabelArtifact; import org.onap.aai.modelloader.config.ModelLoaderConfig; @@ -74,7 +74,7 @@ public class TestArtifactDownloadManager { private BabelArtifactConverter mockBabelArtifactConverter; private BabelServiceClientFactory mockClientFactory; - @Before + @BeforeEach public void setup() throws Exception { mockBabelClient = mock(BabelServiceClient.class); mockDistributionClient = mock(IDistributionClient.class); @@ -92,7 +92,7 @@ public class TestArtifactDownloadManager { ReflectionTestUtils.setField(downloadManager, "babelArtifactConverter", mockBabelArtifactConverter); } - @After + @AfterEach public void tearDown() { downloadManager = null; mockDistributionClient = null; diff --git a/src/test/java/org/onap/aai/modelloader/notification/TestBabelArtifactConverter.java b/src/test/java/org/onap/aai/modelloader/notification/TestBabelArtifactConverter.java index 2a04ec5..1d81513 100644 --- a/src/test/java/org/onap/aai/modelloader/notification/TestBabelArtifactConverter.java +++ b/src/test/java/org/onap/aai/modelloader/notification/TestBabelArtifactConverter.java @@ -20,14 +20,16 @@ */ package org.onap.aai.modelloader.notification; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; import java.util.ArrayList; import java.util.List; -import org.junit.Test; + +import org.junit.jupiter.api.Test; import org.onap.aai.babel.service.data.BabelArtifact; import org.onap.aai.modelloader.entity.Artifact; import org.onap.aai.modelloader.entity.ArtifactType; @@ -42,30 +44,34 @@ import org.onap.sdc.api.notification.INotificationData; */ public class TestBabelArtifactConverter { - @Test(expected = NullPointerException.class) + @Test public void convert_nullToscaFiles() throws BabelArtifactParsingException { - new BabelArtifactConverter().convertToModel(null); - fail("An instance of ArtifactGenerationException should have been thrown"); + assertThrows(NullPointerException.class, () -> { + new BabelArtifactConverter().convertToModel(null); + fail("An instance of ArtifactGenerationException should have been thrown"); + }); } @Test public void testEmptyToscaFiles() throws BabelArtifactParsingException { - assertTrue("Nothing should have been returned", - new BabelArtifactConverter().convertToModel(new ArrayList<>()).isEmpty()); + assertTrue(new BabelArtifactConverter().convertToModel(new ArrayList<>()).isEmpty(), + "Nothing should have been returned"); } - @Test(expected = BabelArtifactParsingException.class) + @Test public void testInvalidXml() throws IOException, BabelArtifactParsingException { - byte[] problemXml = - "This is some xml that should cause the model artifact parser to throw an erorr" - .getBytes(); + assertThrows(BabelArtifactParsingException.class, () -> { + byte[] problemXml = + "This is some xml that should cause the model artifact parser to throw an erorr" + .getBytes(); - INotificationData data = NotificationDataFixtureBuilder.getNotificationDataWithToscaCsarFile(); + INotificationData data = NotificationDataFixtureBuilder.getNotificationDataWithToscaCsarFile(); - List toscaArtifacts = setupTest(problemXml, data); + List toscaArtifacts = setupTest(problemXml, data); - new BabelArtifactConverter().convertToModel(toscaArtifacts); - fail("An instance of ModelArtifactParsingException should have been thrown"); + new BabelArtifactConverter().convertToModel(toscaArtifacts); + fail("An instance of ModelArtifactParsingException should have been thrown"); + }); } private List setupTest(byte[] xml, INotificationData data) throws IOException { @@ -88,7 +94,7 @@ public class TestBabelArtifactConverter { List modelArtifacts = new BabelArtifactConverter().convertToModel(toscaArtifacts); - assertEquals("There should have been 1 artifact", 1, modelArtifacts.size()); + assertEquals(1, modelArtifacts.size(), "There should have been 1 artifact"); assertEquals(new String(xml), modelArtifacts.get(0).getPayload()); assertEquals(ArtifactType.MODEL, modelArtifacts.get(0).getType()); } diff --git a/src/test/java/org/onap/aai/modelloader/notification/TestEventCallback.java b/src/test/java/org/onap/aai/modelloader/notification/TestEventCallback.java index 8c4ffed..d9245d9 100644 --- a/src/test/java/org/onap/aai/modelloader/notification/TestEventCallback.java +++ b/src/test/java/org/onap/aai/modelloader/notification/TestEventCallback.java @@ -29,9 +29,9 @@ import java.io.IOException; import java.util.List; import java.util.Properties; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.onap.aai.modelloader.config.ModelLoaderConfig; import org.onap.aai.modelloader.entity.model.BabelArtifactParsingException; @@ -57,7 +57,7 @@ public class TestEventCallback { private IDistributionClient mockDistributionClient; private NotificationPublisher mockNotificationPublisher; - @Before + @BeforeEach public void setup() throws IOException { configProperties = new Properties(); configProperties.load(this.getClass().getClassLoader().getResourceAsStream(CONFIG_FILE)); @@ -75,7 +75,7 @@ public class TestEventCallback { ReflectionTestUtils.setField(eventCallback, "notificationPublisher", mockNotificationPublisher); } - @After + @AfterEach public void tearDown() { config = null; configProperties = null; diff --git a/src/test/java/org/onap/aai/modelloader/notification/TestNotificationDataImpl.java b/src/test/java/org/onap/aai/modelloader/notification/TestNotificationDataImpl.java index d7441ec..4d6858a 100644 --- a/src/test/java/org/onap/aai/modelloader/notification/TestNotificationDataImpl.java +++ b/src/test/java/org/onap/aai/modelloader/notification/TestNotificationDataImpl.java @@ -25,7 +25,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Tests for NotificationDataImpl class diff --git a/src/test/java/org/onap/aai/modelloader/notification/TestNotificationPublisher.java b/src/test/java/org/onap/aai/modelloader/notification/TestNotificationPublisher.java index 253fc17..edf50a8 100644 --- a/src/test/java/org/onap/aai/modelloader/notification/TestNotificationPublisher.java +++ b/src/test/java/org/onap/aai/modelloader/notification/TestNotificationPublisher.java @@ -20,13 +20,13 @@ */ package org.onap.aai.modelloader.notification; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.onap.sdc.api.IDistributionClient; @@ -61,7 +61,7 @@ public class TestNotificationPublisher { System.setProperty("CONFIG_HOME", "src/test/resources"); } - @Before + @BeforeEach public void setupMocks() { MockitoAnnotations.initMocks(this); when(client.getConfiguration()).thenReturn(config); diff --git a/src/test/java/org/onap/aai/modelloader/restclient/TestAaiRestClient.java b/src/test/java/org/onap/aai/modelloader/restclient/TestAaiRestClient.java index ebdfcfe..37b7e12 100644 --- a/src/test/java/org/onap/aai/modelloader/restclient/TestAaiRestClient.java +++ b/src/test/java/org/onap/aai/modelloader/restclient/TestAaiRestClient.java @@ -20,7 +20,7 @@ */ package org.onap.aai.modelloader.restclient; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; import java.io.IOException; diff --git a/src/test/java/org/onap/aai/modelloader/restclient/TestAaiServiceClient.java b/src/test/java/org/onap/aai/modelloader/restclient/TestAaiServiceClient.java index c5da065..fd65383 100644 --- a/src/test/java/org/onap/aai/modelloader/restclient/TestAaiServiceClient.java +++ b/src/test/java/org/onap/aai/modelloader/restclient/TestAaiServiceClient.java @@ -22,7 +22,7 @@ package org.onap.aai.modelloader.restclient; import static javax.servlet.http.HttpServletResponse.SC_OK; import static org.apache.commons.io.IOUtils.write; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.nio.charset.Charset; @@ -37,9 +37,9 @@ import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onap.aai.modelloader.config.ModelLoaderConfig; /** @@ -51,7 +51,7 @@ public class TestAaiServiceClient { private Server server; private AaiRestClient aaiClient; - @Before + @BeforeEach public void startJetty() throws Exception { server = new Server(8080); server.setHandler(getMockHandler()); @@ -63,7 +63,7 @@ public class TestAaiServiceClient { aaiClient = new AaiRestClient(config); } - @After + @AfterEach public void stopJetty() throws Exception { server.stop(); } diff --git a/src/test/java/org/onap/aai/modelloader/restclient/TestBabelServiceClient.java b/src/test/java/org/onap/aai/modelloader/restclient/TestBabelServiceClient.java index 51c5642..c653244 100644 --- a/src/test/java/org/onap/aai/modelloader/restclient/TestBabelServiceClient.java +++ b/src/test/java/org/onap/aai/modelloader/restclient/TestBabelServiceClient.java @@ -43,9 +43,9 @@ import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onap.aai.babel.service.data.BabelArtifact; import org.onap.aai.modelloader.config.ModelLoaderConfig; import org.onap.aai.modelloader.service.HttpsBabelServiceClientFactory; @@ -61,7 +61,7 @@ public class TestBabelServiceClient { private Server server; private String responseBody; - @Before + @BeforeEach public void startJetty() throws Exception { List response = new ArrayList<>(); response.add(new BabelArtifact("", null, "")); @@ -74,7 +74,7 @@ public class TestBabelServiceClient { server.start(); } - @After + @AfterEach public void stopJetty() throws Exception { server.stop(); } diff --git a/src/test/java/org/onap/aai/modelloader/service/TestArtifactInfoImpl.java b/src/test/java/org/onap/aai/modelloader/service/TestArtifactInfoImpl.java index a791b46..d1d46f0 100644 --- a/src/test/java/org/onap/aai/modelloader/service/TestArtifactInfoImpl.java +++ b/src/test/java/org/onap/aai/modelloader/service/TestArtifactInfoImpl.java @@ -27,7 +27,7 @@ import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Tests for NotificationDataImpl class diff --git a/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderService.java b/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderService.java index 1a55580..f926d8e 100644 --- a/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderService.java +++ b/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderService.java @@ -22,27 +22,24 @@ package org.onap.aai.modelloader.service; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.util.Base64; import javax.ws.rs.core.Response; -import org.junit.After; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; import org.onap.aai.modelloader.util.ArtifactTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringRunner; /** * Tests for the ModelLoaderService class. * */ -@RunWith(SpringRunner.class) @SpringBootTest(classes = {ModelLoaderService.class, MockBabelServiceClientFactory.class}) @TestPropertySource(properties = {"CONFIG_HOME=src/test/resources",}) public class TestModelLoaderService { @@ -50,7 +47,7 @@ public class TestModelLoaderService { @Autowired private ModelLoaderService service; - @After + @AfterEach public void shutdown() { service.preShutdownOperations(); } diff --git a/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderServiceWithSdc.java b/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderServiceWithSdc.java index 976acbc..6a01bfc 100644 --- a/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderServiceWithSdc.java +++ b/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderServiceWithSdc.java @@ -28,20 +28,17 @@ import java.util.Base64; import javax.ws.rs.core.Response; -import org.junit.After; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; import org.onap.aai.modelloader.util.ArtifactTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringRunner; /** * Tests for the ModelLoaderService class. * */ -@RunWith(SpringRunner.class) @SpringBootTest(classes = {ModelLoaderService.class, HttpsBabelServiceClientFactory.class}) @TestPropertySource(properties = {"CONFIG_HOME=src/test/resources",}) public class TestModelLoaderServiceWithSdc { @@ -49,7 +46,7 @@ public class TestModelLoaderServiceWithSdc { @Autowired private ModelLoaderService service; - @After + @AfterEach public void shutdown() { service.preShutdownOperations(); } diff --git a/src/test/java/org/onap/aai/modelloader/util/TestGizmoTranslator.java b/src/test/java/org/onap/aai/modelloader/util/TestGizmoTranslator.java index 1746e05..c1a862a 100644 --- a/src/test/java/org/onap/aai/modelloader/util/TestGizmoTranslator.java +++ b/src/test/java/org/onap/aai/modelloader/util/TestGizmoTranslator.java @@ -23,19 +23,22 @@ package org.onap.aai.modelloader.util; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onap.aai.modelloader.gizmo.GizmoBulkPayload; public class TestGizmoTranslator { - @Test(expected = IOException.class) + @Test public void translateInvalidXml() throws IOException { - GizmoTranslator.translate("not valid XML"); + assertThrows(IOException.class, () -> { + GizmoTranslator.translate("not valid XML"); + }); } @Test diff --git a/src/test/java/org/onap/aai/modelloader/util/TestJsonXmlConverter.java b/src/test/java/org/onap/aai/modelloader/util/TestJsonXmlConverter.java index 116e1e5..ec66a59 100644 --- a/src/test/java/org/onap/aai/modelloader/util/TestJsonXmlConverter.java +++ b/src/test/java/org/onap/aai/modelloader/util/TestJsonXmlConverter.java @@ -30,7 +30,7 @@ import java.nio.file.Paths; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; diff --git a/version.properties b/version.properties index 78f179d..76cb5aa 100644 --- a/version.properties +++ b/version.properties @@ -24,8 +24,8 @@ # because they are used in Jenkins, whose plug-in doesn't support major=1 -minor=12 -patch=0 +minor=13 +patch=5 base_version=${major}.${minor}.${patch} -- 2.16.6 From 0d67f83c546449bfc01cdce13b48f47a022de363 Mon Sep 17 00:00:00 2001 From: Fiete Ostkamp Date: Wed, 28 Feb 2024 16:30:00 +0100 Subject: [PATCH 04/16] Add tracing to model-loader Issue-ID: AAI-3789 Change-Id: I7ded82e560e07da74883e6a5d7cc61835470d55c Signed-off-by: Fiete Ostkamp --- pom.xml | 17 +++++++++++++++++ src/main/resources/application.properties | 11 +++++++++++ 2 files changed, 28 insertions(+) diff --git a/pom.xml b/pom.xml index a311908..9b4c510 100644 --- a/pom.xml +++ b/pom.xml @@ -44,6 +44,13 @@ pom import + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + @@ -52,6 +59,7 @@ https://nexus.onap.org ${basedir}/target 2.4.13 + 2020.0.2 1.10.0 1.22 1.3 @@ -214,6 +222,7 @@ + org.springframework.boot @@ -322,6 +331,14 @@ commons-text ${apache.commons-text.version} + + org.springframework.cloud + spring-cloud-starter-sleuth + + + org.springframework.cloud + spring-cloud-sleuth-zipkin + diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 7273277..d3b4f45 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -12,6 +12,17 @@ server.port=9500 #server.ssl.keyStoreType= #server.ssl.keyAlias= +spring.application.name=model-loader + +spring.sleuth.enabled=false +spring.zipkin.baseUrl=http://jaeger-collector.istio-system:9411 +spring.sleuth.messaging.jms.enabled=false +spring.sleuth.trace-id128=true +spring.sleuth.sampler.probability=1.0 +spring.sleuth.propagation.type=w3c,b3 +spring.sleuth.supports-join=false +spring.sleuth.web.skip-pattern=/aai/util.* + server.tomcat.threads.max=200 # The minimum number of threads always kept alive server.tomcat.threads.min-spare=25 -- 2.16.6 From 5f6ec01eb82e250120e460c4de7b4c66fb440920 Mon Sep 17 00:00:00 2001 From: Fiete Ostkamp Date: Thu, 7 Mar 2024 08:33:48 +0100 Subject: [PATCH 05/16] Update sdc-distribution-client and vulnerable dependencies in model-loader - update sdc-distribution-client from 2.0.0 to 2.1.1 - update gson from 2.8.9 to 2.10.1 - update json-sanitizer from 1.2.0 to 1.2.3 Issue-ID: AAI-3804 Change-Id: Id5666f38fac054f71fc003fe80867c928f9e0937 Signed-off-by: Fiete Ostkamp --- pom.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 9b4c510..8f2beeb 100644 --- a/pom.xml +++ b/pom.xml @@ -62,10 +62,11 @@ 2020.0.2 1.10.0 1.22 + 2.10.1 1.3 1.9.5 1.2.1 - 2.0.0 + 2.1.1 1.2.11 0.39.0 @@ -268,6 +269,7 @@ com.google.code.gson gson + ${gson.version} @@ -275,7 +277,7 @@ com.mikesamuel json-sanitizer - 1.2.0 + 1.2.3 -- 2.16.6 From ea65a8ab0b5f2f75d56ddddf8f2b436fa4666785 Mon Sep 17 00:00:00 2001 From: Fiete Ostkamp Date: Tue, 26 Mar 2024 08:54:40 +0100 Subject: [PATCH 06/16] Refactor model controller in model-loader - rename ModelLoaderService to ModelController since it's a @RestController - use dependency injection for depending classes - make class as immutable as possible Issue-ID: AAI-3806 Change-Id: I3b976f2c4ed3dba43e8696eb9f6e0d7575403963 Signed-off-by: Fiete Ostkamp --- .../onap/aai/modelloader/config/BeanConfig.java | 68 ++++++++++++++++++++++ ...odelLoaderService.java => ModelController.java} | 56 +++++------------- .../service/TestModelLoaderService.java | 12 +--- .../service/TestModelLoaderServiceWithSdc.java | 5 +- 4 files changed, 89 insertions(+), 52 deletions(-) create mode 100644 src/main/java/org/onap/aai/modelloader/config/BeanConfig.java rename src/main/java/org/onap/aai/modelloader/service/{ModelLoaderService.java => ModelController.java} (82%) diff --git a/src/main/java/org/onap/aai/modelloader/config/BeanConfig.java b/src/main/java/org/onap/aai/modelloader/config/BeanConfig.java new file mode 100644 index 0000000..2d7775d --- /dev/null +++ b/src/main/java/org/onap/aai/modelloader/config/BeanConfig.java @@ -0,0 +1,68 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2024 Deutsche Telekom AG Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +package org.onap.aai.modelloader.config; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Properties; + +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.modelloader.notification.EventCallback; +import org.onap.aai.modelloader.service.BabelServiceClientFactory; +import org.onap.aai.modelloader.service.ModelLoaderMsgs; +import org.onap.sdc.api.IDistributionClient; +import org.onap.sdc.impl.DistributionClientFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class BeanConfig { + + private static final Logger logger = LoggerFactory.getInstance().getLogger(BeanConfig.class); + + + @Value("${CONFIG_HOME}") + private String configDir; + + @Bean + public ModelLoaderConfig modelLoaderConfig() throws IOException { + // Load model loader system configuration + logger.info(ModelLoaderMsgs.LOADING_CONFIGURATION); + ModelLoaderConfig.setConfigHome(configDir); + Properties configProperties = new Properties(); + InputStream configInputStream = Files.newInputStream(Paths.get(configDir, "model-loader.properties")); + configProperties.load(configInputStream); + return new ModelLoaderConfig(configProperties); + } + + @Bean + public IDistributionClient iDistributionClient() { + return DistributionClientFactory.createDistributionClient(); + } + + @Bean + public EventCallback eventCallback(IDistributionClient client, ModelLoaderConfig config, BabelServiceClientFactory babelClientFactory) { + return new EventCallback(client, config, babelClientFactory); + } +} diff --git a/src/main/java/org/onap/aai/modelloader/service/ModelLoaderService.java b/src/main/java/org/onap/aai/modelloader/service/ModelController.java similarity index 82% rename from src/main/java/org/onap/aai/modelloader/service/ModelLoaderService.java rename to src/main/java/org/onap/aai/modelloader/service/ModelController.java index dc17dfe..0ca2c02 100644 --- a/src/main/java/org/onap/aai/modelloader/service/ModelLoaderService.java +++ b/src/main/java/org/onap/aai/modelloader/service/ModelController.java @@ -21,18 +21,13 @@ package org.onap.aai.modelloader.service; import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Base64; import java.util.Date; import java.util.List; -import java.util.Properties; import java.util.Timer; import java.util.TimerTask; -import javax.annotation.PostConstruct; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @@ -47,10 +42,7 @@ import org.onap.aai.modelloader.notification.NotificationPublisher; import org.onap.sdc.api.IDistributionClient; import org.onap.sdc.api.notification.IArtifactInfo; import org.onap.sdc.api.results.IDistributionClientResult; -import org.onap.sdc.impl.DistributionClientFactory; import org.onap.sdc.utils.DistributionActionResultEnum; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -61,36 +53,21 @@ import org.springframework.web.bind.annotation.RestController; */ @RestController @RequestMapping("/services/model-loader/v1/model-service") -public class ModelLoaderService implements ModelLoaderInterface { +public class ModelController implements ModelLoaderInterface { - private static final Logger logger = LoggerFactory.getInstance().getLogger(ModelLoaderService.class.getName()); + private static final Logger logger = LoggerFactory.getInstance().getLogger(ModelController.class.getName()); - @Value("${CONFIG_HOME}") - private String configDir; - private IDistributionClient client; - private ModelLoaderConfig config; - @Autowired - private BabelServiceClientFactory babelClientFactory; + private final IDistributionClient client; + private final ModelLoaderConfig config; + private final EventCallback eventCallback; + private final BabelServiceClientFactory babelClientFactory; - /** - * Responsible for loading configuration files and calling initialization. - */ - @PostConstruct - protected void start() { - // Load model loader system configuration - logger.info(ModelLoaderMsgs.LOADING_CONFIGURATION); - ModelLoaderConfig.setConfigHome(configDir); - Properties configProperties = new Properties(); - try (InputStream configInputStream = Files.newInputStream(Paths.get(configDir, "model-loader.properties"))) { - configProperties.load(configInputStream); - config = new ModelLoaderConfig(configProperties); - if (!config.getASDCConnectionDisabled()) { - initSdcClient(); - } - } catch (IOException e) { - String errorMsg = "Failed to load configuration: " + e.getMessage(); - logger.error(ModelLoaderMsgs.ASDC_CONNECTION_ERROR, errorMsg); - } + public ModelController(IDistributionClient client, ModelLoaderConfig config, EventCallback eventCallback, + BabelServiceClientFactory babelClientFactory) { + this.client = client; + this.config = config; + this.eventCallback = eventCallback; + this.babelClientFactory = babelClientFactory; } /** @@ -109,10 +86,7 @@ public class ModelLoaderService implements ModelLoaderInterface { protected void initSdcClient() { // Initialize distribution client logger.debug(ModelLoaderMsgs.INITIALIZING, "Initializing distribution client..."); - client = DistributionClientFactory.createDistributionClient(); - EventCallback callback = new EventCallback(client, config, babelClientFactory); - - IDistributionClientResult initResult = client.init(config, callback); + IDistributionClientResult initResult = client.init(config, eventCallback); if (initResult.getDistributionActionResult() == DistributionActionResultEnum.SUCCESS) { // Start distribution client @@ -126,7 +100,7 @@ public class ModelLoaderService implements ModelLoaderInterface { // Kick off a timer to retry the SDC connection Timer timer = new Timer(); - TimerTask task = new SdcConnectionJob(client, config, callback, timer); + TimerTask task = new SdcConnectionJob(client, config, eventCallback, timer); timer.schedule(task, new Date(), 60000); } } else { @@ -135,7 +109,7 @@ public class ModelLoaderService implements ModelLoaderInterface { // Kick off a timer to retry the SDC connection Timer timer = new Timer(); - TimerTask task = new SdcConnectionJob(client, config, callback, timer); + TimerTask task = new SdcConnectionJob(client, config, eventCallback, timer); timer.schedule(task, new Date(), 60000); } diff --git a/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderService.java b/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderService.java index f926d8e..e58716c 100644 --- a/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderService.java +++ b/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderService.java @@ -22,7 +22,6 @@ package org.onap.aai.modelloader.service; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.util.Base64; @@ -31,6 +30,7 @@ import javax.ws.rs.core.Response; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.onap.aai.modelloader.config.BeanConfig; import org.onap.aai.modelloader.util.ArtifactTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -40,24 +40,18 @@ import org.springframework.test.context.TestPropertySource; * Tests for the ModelLoaderService class. * */ -@SpringBootTest(classes = {ModelLoaderService.class, MockBabelServiceClientFactory.class}) +@SpringBootTest(classes = {BeanConfig.class, ModelController.class, MockBabelServiceClientFactory.class}) @TestPropertySource(properties = {"CONFIG_HOME=src/test/resources",}) public class TestModelLoaderService { @Autowired - private ModelLoaderService service; + private ModelController service; @AfterEach public void shutdown() { service.preShutdownOperations(); } - @Test - public void testMissingConfig() { - new ModelLoaderService().start(); - assertTrue(true); - } - @Test public void testLoadModel() { Response response = service.loadModel(""); diff --git a/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderServiceWithSdc.java b/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderServiceWithSdc.java index 6a01bfc..0b68cc0 100644 --- a/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderServiceWithSdc.java +++ b/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderServiceWithSdc.java @@ -30,6 +30,7 @@ import javax.ws.rs.core.Response; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.onap.aai.modelloader.config.BeanConfig; import org.onap.aai.modelloader.util.ArtifactTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -39,12 +40,12 @@ import org.springframework.test.context.TestPropertySource; * Tests for the ModelLoaderService class. * */ -@SpringBootTest(classes = {ModelLoaderService.class, HttpsBabelServiceClientFactory.class}) +@SpringBootTest(classes = {BeanConfig.class, ModelController.class, HttpsBabelServiceClientFactory.class}) @TestPropertySource(properties = {"CONFIG_HOME=src/test/resources",}) public class TestModelLoaderServiceWithSdc { @Autowired - private ModelLoaderService service; + private ModelController service; @AfterEach public void shutdown() { -- 2.16.6 From ba282826d84f17e3156de99c39e6b1d50813f8ba Mon Sep 17 00:00:00 2001 From: Fiete Ostkamp Date: Tue, 26 Mar 2024 09:32:42 +0100 Subject: [PATCH 07/16] Publish 1.13.5 maven release - add tracing - update spring boot from 2.1 to 2.4 - update docker base image Issue-ID: AAI-3808 Change-Id: I7a7f3314cd3900c98300fed71dda1399eb91bfa2 Signed-off-by: Fiete Ostkamp --- releases/1.13.5-maven-release.yaml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 releases/1.13.5-maven-release.yaml diff --git a/releases/1.13.5-maven-release.yaml b/releases/1.13.5-maven-release.yaml new file mode 100644 index 0000000..71cc585 --- /dev/null +++ b/releases/1.13.5-maven-release.yaml @@ -0,0 +1,4 @@ +distribution_type: maven +log_dir: aai-model-loader-maven-stage-master/1396/ +project: model-loader +version: 1.13.5 -- 2.16.6 From 277fb3d9331b8a5c7fbdd8cf72cb623963a1ff4b Mon Sep 17 00:00:00 2001 From: Fiete Ostkamp Date: Tue, 26 Mar 2024 09:55:53 +0100 Subject: [PATCH 08/16] Publish 1.13.5 container release - add tracing - update spring boot from 2.1 to 2.4 - update docker base image Issue-ID: AAI-3809 Change-Id: If5e56ebded9592c74f67808ca3f059b0e16a20be Signed-off-by: Fiete Ostkamp --- pom.xml | 2 +- releases/1.13.5-container-release.yaml | 7 +++++++ version.properties | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 releases/1.13.5-container-release.yaml diff --git a/pom.xml b/pom.xml index 8f2beeb..61a4ec2 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ org.onap.aai.model-loader model-loader aai-model-loader - 1.13.5-SNAPSHOT + 1.13.6-SNAPSHOT diff --git a/releases/1.13.5-container-release.yaml b/releases/1.13.5-container-release.yaml new file mode 100644 index 0000000..0177137 --- /dev/null +++ b/releases/1.13.5-container-release.yaml @@ -0,0 +1,7 @@ +distribution_type: container +container_release_tag: 1.13.5 +project: model-loader +ref: ba282826d84f17e3156de99c39e6b1d50813f8ba +containers: + - name: model-loader + version: 1.13-STAGING-20240326T082727Z diff --git a/version.properties b/version.properties index 76cb5aa..b2aac49 100644 --- a/version.properties +++ b/version.properties @@ -25,7 +25,7 @@ major=1 minor=13 -patch=5 +patch=6 base_version=${major}.${minor}.${patch} -- 2.16.6 From 19f034b2554895285f12979b0f36c1629f9af984 Mon Sep 17 00:00:00 2001 From: Fiete Ostkamp Date: Tue, 26 Mar 2024 11:28:49 +0100 Subject: [PATCH 09/16] Use more dependency injection in model-loader - use more dependency injection - make class variables final if possible - simplify mock creation in some tests using @Mock Issue-ID: AAI-3811 Change-Id: I7a7ccba02df78e6fd3bf082c23aac0968137661b Signed-off-by: Fiete Ostkamp --- .../onap/aai/modelloader/config/BeanConfig.java | 7 -- .../entity/catalog/VnfCatalogArtifactHandler.java | 2 + .../entity/model/ModelArtifactHandler.java | 2 + .../extraction/VnfCatalogExtractor.java | 2 + .../notification/ArtifactDownloadManager.java | 55 +++++----------- .../notification/BabelArtifactConverter.java | 4 +- .../modelloader/notification/EventCallback.java | 58 +++++------------ .../notification/NotificationPublisher.java | 2 + .../restclient/HttpsBabelServiceClient.java | 3 +- .../service/ArtifactDeploymentManager.java | 38 ++++------- .../aai/modelloader/service/ModelController.java | 15 +++-- .../aai/modelloader/service/SdcConnectionJob.java | 7 +- .../ArtifactDownloadManagerVnfcTest.java | 32 ++++----- .../TestArtifactDeploymentManager.java | 76 ++++++++++------------ .../notification/TestArtifactDownloadManager.java | 28 ++++---- .../notification/TestEventCallback.java | 28 +++----- .../notification/TestNotificationPublisher.java | 2 +- ...LoaderService.java => TestModelController.java} | 52 ++++++++++++--- .../service/TestModelLoaderServiceWithSdc.java | 3 +- 19 files changed, 182 insertions(+), 234 deletions(-) rename src/test/java/org/onap/aai/modelloader/service/{TestModelLoaderService.java => TestModelController.java} (51%) diff --git a/src/main/java/org/onap/aai/modelloader/config/BeanConfig.java b/src/main/java/org/onap/aai/modelloader/config/BeanConfig.java index 2d7775d..8f7b2bb 100644 --- a/src/main/java/org/onap/aai/modelloader/config/BeanConfig.java +++ b/src/main/java/org/onap/aai/modelloader/config/BeanConfig.java @@ -27,8 +27,6 @@ import java.util.Properties; import org.onap.aai.cl.api.Logger; import org.onap.aai.cl.eelf.LoggerFactory; -import org.onap.aai.modelloader.notification.EventCallback; -import org.onap.aai.modelloader.service.BabelServiceClientFactory; import org.onap.aai.modelloader.service.ModelLoaderMsgs; import org.onap.sdc.api.IDistributionClient; import org.onap.sdc.impl.DistributionClientFactory; @@ -60,9 +58,4 @@ public class BeanConfig { public IDistributionClient iDistributionClient() { return DistributionClientFactory.createDistributionClient(); } - - @Bean - public EventCallback eventCallback(IDistributionClient client, ModelLoaderConfig config, BabelServiceClientFactory babelClientFactory) { - return new EventCallback(client, config, babelClientFactory); - } } diff --git a/src/main/java/org/onap/aai/modelloader/entity/catalog/VnfCatalogArtifactHandler.java b/src/main/java/org/onap/aai/modelloader/entity/catalog/VnfCatalogArtifactHandler.java index c54d7b2..c1f6e9b 100644 --- a/src/main/java/org/onap/aai/modelloader/entity/catalog/VnfCatalogArtifactHandler.java +++ b/src/main/java/org/onap/aai/modelloader/entity/catalog/VnfCatalogArtifactHandler.java @@ -44,6 +44,7 @@ import org.onap.aai.modelloader.entity.ArtifactHandler; import org.onap.aai.modelloader.restclient.AaiRestClient; import org.onap.aai.modelloader.service.ModelLoaderMsgs; import org.onap.aai.restclient.client.OperationResult; +import org.springframework.stereotype.Component; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -53,6 +54,7 @@ import org.xml.sax.InputSource; /** * VNF Catalog specific handling */ +@Component public class VnfCatalogArtifactHandler extends ArtifactHandler { private static Logger logger = LoggerFactory.getInstance().getLogger(VnfCatalogArtifactHandler.class.getName()); diff --git a/src/main/java/org/onap/aai/modelloader/entity/model/ModelArtifactHandler.java b/src/main/java/org/onap/aai/modelloader/entity/model/ModelArtifactHandler.java index 626ca49..7aa2191 100644 --- a/src/main/java/org/onap/aai/modelloader/entity/model/ModelArtifactHandler.java +++ b/src/main/java/org/onap/aai/modelloader/entity/model/ModelArtifactHandler.java @@ -28,7 +28,9 @@ import org.onap.aai.modelloader.entity.Artifact; import org.onap.aai.modelloader.entity.ArtifactHandler; import org.onap.aai.modelloader.restclient.AaiRestClient; import org.onap.aai.modelloader.service.ModelLoaderMsgs; +import org.springframework.stereotype.Service; +@Service public class ModelArtifactHandler extends ArtifactHandler { private static Logger logger = LoggerFactory.getInstance().getLogger(ModelArtifactHandler.class.getName()); diff --git a/src/main/java/org/onap/aai/modelloader/extraction/VnfCatalogExtractor.java b/src/main/java/org/onap/aai/modelloader/extraction/VnfCatalogExtractor.java index 3dbdfcc..64ecf8a 100644 --- a/src/main/java/org/onap/aai/modelloader/extraction/VnfCatalogExtractor.java +++ b/src/main/java/org/onap/aai/modelloader/extraction/VnfCatalogExtractor.java @@ -38,6 +38,7 @@ import org.onap.aai.modelloader.entity.Artifact; import org.onap.aai.modelloader.entity.ArtifactType; import org.onap.aai.modelloader.entity.catalog.VnfCatalogArtifact; import org.onap.aai.modelloader.service.ModelLoaderMsgs; +import org.springframework.stereotype.Component; /** @@ -47,6 +48,7 @@ import org.onap.aai.modelloader.service.ModelLoaderMsgs; * A .csar file is a compressed archive like a zip file and this class will treat the byte array as it if were a zip * file. */ +@Component public class VnfCatalogExtractor { private static final Logger logger = LoggerFactory.getInstance().getLogger(VnfCatalogExtractor.class.getName()); diff --git a/src/main/java/org/onap/aai/modelloader/notification/ArtifactDownloadManager.java b/src/main/java/org/onap/aai/modelloader/notification/ArtifactDownloadManager.java index 58bb074..dcec799 100644 --- a/src/main/java/org/onap/aai/modelloader/notification/ArtifactDownloadManager.java +++ b/src/main/java/org/onap/aai/modelloader/notification/ArtifactDownloadManager.java @@ -49,6 +49,7 @@ import org.onap.sdc.api.notification.INotificationData; import org.onap.sdc.api.results.IDistributionClientDownloadResult; import org.onap.sdc.utils.ArtifactTypeEnum; import org.onap.sdc.utils.DistributionActionResultEnum; +import org.springframework.stereotype.Component; /** * This class is responsible for downloading the artifacts from the ASDC. @@ -60,22 +61,26 @@ import org.onap.sdc.utils.DistributionActionResultEnum; * * TOSCA_CSAR file artifacts will be converted into XML and returned as model artifacts. */ +@Component public class ArtifactDownloadManager { private static Logger logger = LoggerFactory.getInstance().getLogger(ArtifactDownloadManager.class); - private IDistributionClient client; - private NotificationPublisher notificationPublisher; - private BabelArtifactConverter babelArtifactConverter; - private ModelLoaderConfig config; - private BabelServiceClientFactory clientFactory; - private VnfCatalogExtractor vnfCatalogExtractor; + private final IDistributionClient client; + private final NotificationPublisher notificationPublisher; + private final BabelArtifactConverter babelArtifactConverter; + private final ModelLoaderConfig config; + private final BabelServiceClientFactory clientFactory; + private final VnfCatalogExtractor vnfCatalogExtractor; public ArtifactDownloadManager(IDistributionClient client, ModelLoaderConfig config, - BabelServiceClientFactory clientFactory) { + BabelServiceClientFactory clientFactory, BabelArtifactConverter babelArtifactConverter, NotificationPublisher notificationPublisher, VnfCatalogExtractor vnfCatalogExtractor) { this.client = client; + this.notificationPublisher = notificationPublisher; + this.babelArtifactConverter = babelArtifactConverter; this.config = config; this.clientFactory = clientFactory; + this.vnfCatalogExtractor = vnfCatalogExtractor; } /** @@ -96,10 +101,10 @@ public class ArtifactDownloadManager { IDistributionClientDownloadResult downloadResult = downloadIndividualArtifacts(data, artifact); processDownloadedArtifacts(modelArtifacts, catalogArtifacts, artifact, downloadResult, data); } catch (DownloadFailureException e) { - getNotificationPublisher().publishDownloadFailure(client, data, artifact, e.getMessage()); + notificationPublisher.publishDownloadFailure(client, data, artifact, e.getMessage()); success = false; } catch (Exception e) { - getNotificationPublisher().publishDeployFailure(client, data, artifact); + notificationPublisher.publishDeployFailure(client, data, artifact); success = false; } @@ -126,7 +131,7 @@ public class ArtifactDownloadManager { if (DistributionActionResultEnum.SUCCESS.equals(downloadResult.getDistributionActionResult())) { logger.info(ModelLoaderMsgs.DISTRIBUTION_EVENT, "Downloaded artifact: " + artifact.getArtifactName()); - getNotificationPublisher().publishDownloadSuccess(client, data, artifact); + notificationPublisher.publishDownloadSuccess(client, data, artifact); } else { throw new DownloadFailureException(downloadResult.getDistributionMessageResult()); } @@ -156,7 +161,7 @@ public class ArtifactDownloadManager { invokeBabelService(modelArtifacts, catalogArtifacts, payload, artifactInfo, distributionId, serviceVersion); // Get VNF Catalog artifacts directly from CSAR - List csarCatalogArtifacts = getVnfCatalogExtractor().extract(payload, artifactInfo.getArtifactName()); + List csarCatalogArtifacts = vnfCatalogExtractor.extract(payload, artifactInfo.getArtifactName()); // Throw an error if VNF Catalog data is present in the Babel payload and directly in the CSAR if (!catalogArtifacts.isEmpty() && !csarCatalogArtifacts.isEmpty()) { @@ -187,12 +192,12 @@ public class ArtifactDownloadManager { if (artifactMap.containsKey(BabelArtifact.ArtifactType.MODEL)) { modelArtifacts.addAll( - getBabelArtifactConverter().convertToModel(artifactMap.get(BabelArtifact.ArtifactType.MODEL))); + babelArtifactConverter.convertToModel(artifactMap.get(BabelArtifact.ArtifactType.MODEL))); artifactMap.remove(BabelArtifact.ArtifactType.MODEL); } if (artifactMap.containsKey(BabelArtifact.ArtifactType.VNFCATALOG)) { - catalogArtifacts.addAll(getBabelArtifactConverter() + catalogArtifacts.addAll(babelArtifactConverter .convertToCatalog(artifactMap.get(BabelArtifact.ArtifactType.VNFCATALOG))); artifactMap.remove(BabelArtifact.ArtifactType.VNFCATALOG); } @@ -257,28 +262,4 @@ public class ArtifactDownloadManager { private boolean parsedArtifactsExist(List parsedArtifacts) { return parsedArtifacts != null && !parsedArtifacts.isEmpty(); } - - private NotificationPublisher getNotificationPublisher() { - if (notificationPublisher == null) { - notificationPublisher = new NotificationPublisher(); - } - - return notificationPublisher; - } - - private BabelArtifactConverter getBabelArtifactConverter() { - if (babelArtifactConverter == null) { - babelArtifactConverter = new BabelArtifactConverter(); - } - - return babelArtifactConverter; - } - - private VnfCatalogExtractor getVnfCatalogExtractor() { - if (vnfCatalogExtractor == null) { - vnfCatalogExtractor = new VnfCatalogExtractor(); - } - - return vnfCatalogExtractor; - } } diff --git a/src/main/java/org/onap/aai/modelloader/notification/BabelArtifactConverter.java b/src/main/java/org/onap/aai/modelloader/notification/BabelArtifactConverter.java index ad4eb7d..480a461 100644 --- a/src/main/java/org/onap/aai/modelloader/notification/BabelArtifactConverter.java +++ b/src/main/java/org/onap/aai/modelloader/notification/BabelArtifactConverter.java @@ -29,12 +29,14 @@ import org.onap.aai.modelloader.entity.catalog.VnfCatalogArtifact; import org.onap.aai.modelloader.entity.model.BabelArtifactParsingException; import org.onap.aai.modelloader.entity.model.ModelArtifact; import org.onap.aai.modelloader.entity.model.ModelArtifactParser; +import org.springframework.stereotype.Component; /** * This class is responsible for converting TOSCA artifacts into instances of {@link ModelArtifact} ready for pushing * the converted artifacts . */ -class BabelArtifactConverter { +@Component +public class BabelArtifactConverter { /** * This method converts BabelArtifacts into instances of {@link ModelArtifact}. diff --git a/src/main/java/org/onap/aai/modelloader/notification/EventCallback.java b/src/main/java/org/onap/aai/modelloader/notification/EventCallback.java index d7bccba..754eaff 100644 --- a/src/main/java/org/onap/aai/modelloader/notification/EventCallback.java +++ b/src/main/java/org/onap/aai/modelloader/notification/EventCallback.java @@ -26,33 +26,32 @@ import java.util.List; import org.onap.aai.cl.api.Logger; import org.onap.aai.cl.eelf.LoggerFactory; import org.onap.aai.cl.mdc.MdcContext; -import org.onap.aai.modelloader.config.ModelLoaderConfig; import org.onap.aai.modelloader.entity.Artifact; import org.onap.aai.modelloader.extraction.ArtifactInfoExtractor; import org.onap.aai.modelloader.service.ArtifactDeploymentManager; -import org.onap.aai.modelloader.service.BabelServiceClientFactory; import org.onap.aai.modelloader.service.ModelLoaderMsgs; import org.onap.sdc.api.IDistributionClient; import org.onap.sdc.api.consumer.INotificationCallback; import org.onap.sdc.api.notification.IArtifactInfo; import org.onap.sdc.api.notification.INotificationData; import org.slf4j.MDC; +import org.springframework.stereotype.Component; +@Component public class EventCallback implements INotificationCallback { private static Logger logger = LoggerFactory.getInstance().getLogger(EventCallback.class.getName()); private static Logger auditLogger = LoggerFactory.getInstance().getAuditLogger(EventCallback.class.getName()); - private ArtifactDeploymentManager artifactDeploymentManager; - private ArtifactDownloadManager artifactDownloadManager; - private NotificationPublisher notificationPublisher; - private IDistributionClient client; - private ModelLoaderConfig config; - private BabelServiceClientFactory babelServiceClientFactory; + private final ArtifactDeploymentManager artifactDeploymentManager; + private final ArtifactDownloadManager artifactDownloadManager; + private final NotificationPublisher notificationPublisher; + private final IDistributionClient client; - public EventCallback(IDistributionClient client, ModelLoaderConfig config, BabelServiceClientFactory babelServiceClientFactory) { + public EventCallback(IDistributionClient client, ArtifactDeploymentManager artifactDeploymentManager, ArtifactDownloadManager artifactDownloadManager, NotificationPublisher notificationPublisher) { + this.artifactDeploymentManager = artifactDeploymentManager; + this.artifactDownloadManager = artifactDownloadManager; + this.notificationPublisher = notificationPublisher; this.client = client; - this.config = config; - this.babelServiceClientFactory = babelServiceClientFactory; } @Override @@ -65,10 +64,10 @@ public class EventCallback implements INotificationCallback { List modelArtifacts = new ArrayList<>(); boolean success = - getArtifactDownloadManager().downloadArtifacts(data, artifacts, modelArtifacts, catalogArtifacts); + artifactDownloadManager.downloadArtifacts(data, artifacts, modelArtifacts, catalogArtifacts); if (success) { - success = getArtifactDeploymentManager().deploy(data, modelArtifacts, catalogArtifacts); + success = artifactDeploymentManager.deploy(data, modelArtifacts, catalogArtifacts); } String statusString = success ? "SUCCESS" : "FAILURE"; @@ -85,37 +84,12 @@ public class EventCallback implements INotificationCallback { boolean deploymentSuccess) { if (deploymentSuccess) { artifacts.stream().filter(a -> filterType.equalsIgnoreCase(a.getArtifactType())) - .forEach(a -> getNotificationPublisher().publishDeploySuccess(client, data, a)); - getNotificationPublisher().publishComponentSuccess(client, data); + .forEach(a -> notificationPublisher.publishDeploySuccess(client, data, a)); + notificationPublisher.publishComponentSuccess(client, data); } else { artifacts.stream().filter(a -> filterType.equalsIgnoreCase(a.getArtifactType())) - .forEach(a -> getNotificationPublisher().publishDeployFailure(client, data, a)); - getNotificationPublisher().publishComponentFailure(client, data, "deploy failure"); + .forEach(a -> notificationPublisher.publishDeployFailure(client, data, a)); + notificationPublisher.publishComponentFailure(client, data, "deploy failure"); } } - - private ArtifactDeploymentManager getArtifactDeploymentManager() { - if (artifactDeploymentManager == null) { - artifactDeploymentManager = new ArtifactDeploymentManager(config); - } - - return artifactDeploymentManager; - } - - private ArtifactDownloadManager getArtifactDownloadManager() { - if (artifactDownloadManager == null) { - artifactDownloadManager = new ArtifactDownloadManager(client, config, babelServiceClientFactory); - } - - return artifactDownloadManager; - } - - - private NotificationPublisher getNotificationPublisher() { - if (notificationPublisher == null) { - notificationPublisher = new NotificationPublisher(); - } - - return notificationPublisher; - } } diff --git a/src/main/java/org/onap/aai/modelloader/notification/NotificationPublisher.java b/src/main/java/org/onap/aai/modelloader/notification/NotificationPublisher.java index 6e6ff13..7d5b1cc 100644 --- a/src/main/java/org/onap/aai/modelloader/notification/NotificationPublisher.java +++ b/src/main/java/org/onap/aai/modelloader/notification/NotificationPublisher.java @@ -38,10 +38,12 @@ import org.onap.sdc.api.notification.INotificationData; import org.onap.sdc.api.results.IDistributionClientResult; import org.onap.sdc.utils.DistributionActionResultEnum; import org.onap.sdc.utils.DistributionStatusEnum; +import org.springframework.stereotype.Component; /** * This class is responsible for publishing the status of actions performed working with artifacts. */ +@Component public class NotificationPublisher { private static Logger logger = LoggerFactory.getInstance().getLogger(NotificationPublisher.class); diff --git a/src/main/java/org/onap/aai/modelloader/restclient/HttpsBabelServiceClient.java b/src/main/java/org/onap/aai/modelloader/restclient/HttpsBabelServiceClient.java index 289015c..88967b2 100644 --- a/src/main/java/org/onap/aai/modelloader/restclient/HttpsBabelServiceClient.java +++ b/src/main/java/org/onap/aai/modelloader/restclient/HttpsBabelServiceClient.java @@ -224,7 +224,8 @@ public class HttpsBabelServiceClient implements BabelServiceClient { MdcOverride override = new MdcOverride(); override.addAttribute(MdcContext.MDC_START_TIME, ZonedDateTime.now().format(formatter)); - WebResource webResource = client.resource(config.getBabelBaseUrl() + config.getBabelGenerateArtifactsUrl()); + String resourceUrl = config.getBabelBaseUrl() + config.getBabelGenerateArtifactsUrl(); + WebResource webResource = client.resource(resourceUrl); ClientResponse response = webResource.type("application/json") .header(AaiRestClient.HEADER_TRANS_ID, Collections.singletonList(transactionId)) .header(AaiRestClient.HEADER_FROM_APP_ID, Collections.singletonList(AaiRestClient.ML_APP_NAME)) diff --git a/src/main/java/org/onap/aai/modelloader/service/ArtifactDeploymentManager.java b/src/main/java/org/onap/aai/modelloader/service/ArtifactDeploymentManager.java index af006f5..9f09703 100644 --- a/src/main/java/org/onap/aai/modelloader/service/ArtifactDeploymentManager.java +++ b/src/main/java/org/onap/aai/modelloader/service/ArtifactDeploymentManager.java @@ -28,18 +28,22 @@ import org.onap.aai.modelloader.entity.catalog.VnfCatalogArtifactHandler; import org.onap.aai.modelloader.entity.model.ModelArtifactHandler; import org.onap.aai.modelloader.restclient.AaiRestClient; import org.onap.sdc.api.notification.INotificationData; +import org.springframework.stereotype.Component; /** * This class is responsible for deploying model and catalog artifacts. */ +@Component public class ArtifactDeploymentManager { - private ModelLoaderConfig config; - private ModelArtifactHandler modelArtifactHandler; - private VnfCatalogArtifactHandler vnfCatalogArtifactHandler; + private final ModelLoaderConfig config; + private final ModelArtifactHandler modelArtifactHandler; + private final VnfCatalogArtifactHandler vnfCatalogArtifactHandler; - public ArtifactDeploymentManager(ModelLoaderConfig config) { + public ArtifactDeploymentManager(ModelLoaderConfig config, ModelArtifactHandler modelArtifactHandler, VnfCatalogArtifactHandler vnfCatalogArtifactHandler) { this.config = config; + this.modelArtifactHandler = modelArtifactHandler; + this.vnfCatalogArtifactHandler = vnfCatalogArtifactHandler; } /** @@ -59,36 +63,20 @@ public class ArtifactDeploymentManager { List completedArtifacts = new ArrayList<>(); boolean deploySuccess = - getModelArtifactHandler().pushArtifacts(modelArtifacts, distributionId, completedArtifacts, aaiClient); + modelArtifactHandler.pushArtifacts(modelArtifacts, distributionId, completedArtifacts, aaiClient); if (!deploySuccess) { - getModelArtifactHandler().rollback(completedArtifacts, distributionId, aaiClient); + modelArtifactHandler.rollback(completedArtifacts, distributionId, aaiClient); } else { List completedImageData = new ArrayList<>(); - deploySuccess = getVnfCatalogArtifactHandler().pushArtifacts(catalogArtifacts, distributionId, + deploySuccess = vnfCatalogArtifactHandler.pushArtifacts(catalogArtifacts, distributionId, completedImageData, aaiClient); if (!deploySuccess) { - getModelArtifactHandler().rollback(completedArtifacts, distributionId, aaiClient); - getVnfCatalogArtifactHandler().rollback(completedImageData, distributionId, aaiClient); + modelArtifactHandler.rollback(completedArtifacts, distributionId, aaiClient); + vnfCatalogArtifactHandler.rollback(completedImageData, distributionId, aaiClient); } } return deploySuccess; } - - private ModelArtifactHandler getModelArtifactHandler() { - if (modelArtifactHandler == null) { - modelArtifactHandler = new ModelArtifactHandler(config); - } - - return modelArtifactHandler; - } - - private VnfCatalogArtifactHandler getVnfCatalogArtifactHandler() { - if (vnfCatalogArtifactHandler == null) { - this.vnfCatalogArtifactHandler = new VnfCatalogArtifactHandler(config); - } - - return vnfCatalogArtifactHandler; - } } diff --git a/src/main/java/org/onap/aai/modelloader/service/ModelController.java b/src/main/java/org/onap/aai/modelloader/service/ModelController.java index 0ca2c02..0921982 100644 --- a/src/main/java/org/onap/aai/modelloader/service/ModelController.java +++ b/src/main/java/org/onap/aai/modelloader/service/ModelController.java @@ -55,19 +55,20 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("/services/model-loader/v1/model-service") public class ModelController implements ModelLoaderInterface { - private static final Logger logger = LoggerFactory.getInstance().getLogger(ModelController.class.getName()); + private static final Logger logger = LoggerFactory.getInstance().getLogger(ModelController.class); private final IDistributionClient client; private final ModelLoaderConfig config; private final EventCallback eventCallback; - private final BabelServiceClientFactory babelClientFactory; + private final ArtifactDeploymentManager artifactDeploymentManager; + private final ArtifactDownloadManager artifactDownloadManager; - public ModelController(IDistributionClient client, ModelLoaderConfig config, EventCallback eventCallback, - BabelServiceClientFactory babelClientFactory) { + public ModelController(IDistributionClient client, ModelLoaderConfig config, EventCallback eventCallback, ArtifactDeploymentManager artifactDeploymentManager, ArtifactDownloadManager artifactDownloadManager) { this.client = client; this.config = config; this.eventCallback = eventCallback; - this.babelClientFactory = babelClientFactory; + this.artifactDeploymentManager = artifactDeploymentManager; + this.artifactDownloadManager = artifactDownloadManager; } /** @@ -167,7 +168,7 @@ public class ModelController implements ModelLoaderInterface { List modelArtifacts = new ArrayList<>(); List catalogArtifacts = new ArrayList<>(); - new ArtifactDownloadManager(client, config, babelClientFactory).processToscaArtifacts(modelArtifacts, + artifactDownloadManager.processToscaArtifacts(modelArtifacts, catalogArtifacts, csarFile, artifactInfo, "test-transaction-id", modelVersion); logger.info(ModelLoaderMsgs.DISTRIBUTION_EVENT, "Loading xml models from test artifacts: " @@ -176,7 +177,7 @@ public class ModelController implements ModelLoaderInterface { NotificationDataImpl notificationData = new NotificationDataImpl(); notificationData.setDistributionID("TestDistributionID"); boolean success = - new ArtifactDeploymentManager(config).deploy(notificationData, modelArtifacts, catalogArtifacts); + artifactDeploymentManager.deploy(notificationData, modelArtifacts, catalogArtifacts); logger.info(ModelLoaderMsgs.DISTRIBUTION_EVENT, "Deployment success was " + success); response = success ? Response.ok().build() : Response.serverError().build(); } catch (Exception e) { diff --git a/src/main/java/org/onap/aai/modelloader/service/SdcConnectionJob.java b/src/main/java/org/onap/aai/modelloader/service/SdcConnectionJob.java index 9a6c6e9..79cf538 100644 --- a/src/main/java/org/onap/aai/modelloader/service/SdcConnectionJob.java +++ b/src/main/java/org/onap/aai/modelloader/service/SdcConnectionJob.java @@ -34,12 +34,11 @@ public class SdcConnectionJob extends TimerTask { private static final Logger logger = LoggerFactory.getInstance().getLogger(SdcConnectionJob.class.getName()); private final IDistributionClient client; - private ModelLoaderConfig config; - private EventCallback callback; - private Timer timer; + private final ModelLoaderConfig config; + private final EventCallback callback; + private final Timer timer; public SdcConnectionJob(IDistributionClient client, ModelLoaderConfig config, EventCallback callback, Timer timer) { - super(); this.client = client; this.timer = timer; this.callback = callback; diff --git a/src/test/java/org/onap/aai/modelloader/notification/ArtifactDownloadManagerVnfcTest.java b/src/test/java/org/onap/aai/modelloader/notification/ArtifactDownloadManagerVnfcTest.java index 1b8f33b..aa2d299 100644 --- a/src/test/java/org/onap/aai/modelloader/notification/ArtifactDownloadManagerVnfcTest.java +++ b/src/test/java/org/onap/aai/modelloader/notification/ArtifactDownloadManagerVnfcTest.java @@ -25,7 +25,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.onap.aai.modelloader.fixture.NotificationDataFixtureBuilder.getNotificationDataWithToscaCsarFile; @@ -36,7 +35,9 @@ import java.util.Properties; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; import org.onap.aai.babel.service.data.BabelArtifact; import org.onap.aai.modelloader.config.ModelLoaderConfig; import org.onap.aai.modelloader.entity.Artifact; @@ -56,40 +57,29 @@ import org.onap.sdc.api.notification.INotificationData; import org.onap.sdc.api.results.IDistributionClientDownloadResult; import org.onap.sdc.impl.DistributionClientDownloadResultImpl; import org.onap.sdc.utils.DistributionActionResultEnum; -import org.springframework.test.util.ReflectionTestUtils; /** * Tests {@link ArtifactDownloadManager} with VNF Catalog Artifacts. */ public class ArtifactDownloadManagerVnfcTest { - private ArtifactDownloadManager downloadManager; - private BabelServiceClient mockBabelClient; - private IDistributionClient mockDistributionClient; - private NotificationPublisher mockNotificationPublisher; - private BabelArtifactConverter mockBabelArtifactConverter; - private BabelServiceClientFactory mockClientFactory; - private VnfCatalogExtractor mockVnfCatalogExtractor; + @Mock private ArtifactDownloadManager downloadManager; + @Mock private BabelServiceClient mockBabelClient; + @Mock private IDistributionClient mockDistributionClient; + @Mock private NotificationPublisher mockNotificationPublisher; + @Mock private BabelArtifactConverter mockBabelArtifactConverter; + @Mock private BabelServiceClientFactory mockClientFactory; + @Mock private VnfCatalogExtractor mockVnfCatalogExtractor; @BeforeEach public void setup() throws Exception { - mockBabelClient = mock(BabelServiceClient.class); - mockDistributionClient = mock(IDistributionClient.class); - mockNotificationPublisher = mock(NotificationPublisher.class); - mockBabelArtifactConverter = mock(BabelArtifactConverter.class); - mockClientFactory = mock(BabelServiceClientFactory.class); + MockitoAnnotations.openMocks(this); when(mockClientFactory.create(Mockito.any())).thenReturn(mockBabelClient); - mockVnfCatalogExtractor = mock(VnfCatalogExtractor.class); Properties configProperties = new Properties(); configProperties.load(this.getClass().getClassLoader().getResourceAsStream("model-loader.properties")); downloadManager = new ArtifactDownloadManager(mockDistributionClient, - new ModelLoaderConfig(configProperties, "."), mockClientFactory); - - - ReflectionTestUtils.setField(downloadManager, "notificationPublisher", mockNotificationPublisher); - ReflectionTestUtils.setField(downloadManager, "babelArtifactConverter", mockBabelArtifactConverter); - ReflectionTestUtils.setField(downloadManager, "vnfCatalogExtractor", mockVnfCatalogExtractor); + new ModelLoaderConfig(configProperties, "."), mockClientFactory, mockBabelArtifactConverter, mockNotificationPublisher, mockVnfCatalogExtractor); } @Test diff --git a/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDeploymentManager.java b/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDeploymentManager.java index 2cac3c9..dfced1a 100644 --- a/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDeploymentManager.java +++ b/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDeploymentManager.java @@ -24,7 +24,6 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; 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 static org.onap.aai.modelloader.fixture.NotificationDataFixtureBuilder.getNotificationDataWithCatalogFile; import static org.onap.aai.modelloader.fixture.NotificationDataFixtureBuilder.getNotificationDataWithOneOfEach; @@ -37,7 +36,9 @@ import java.util.Properties; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; import org.onap.aai.babel.service.data.BabelArtifact; import org.onap.aai.modelloader.config.ModelLoaderConfig; import org.onap.aai.modelloader.entity.Artifact; @@ -50,7 +51,6 @@ import org.onap.aai.modelloader.fixture.NotificationDataFixtureBuilder; import org.onap.aai.modelloader.service.ArtifactDeploymentManager; import org.onap.aai.modelloader.util.ArtifactTestUtils; import org.onap.sdc.api.notification.INotificationData; -import org.springframework.test.util.ReflectionTestUtils; /** * Tests {@link ArtifactDeploymentManager}. @@ -63,28 +63,24 @@ public class TestArtifactDeploymentManager { private Properties configProperties; private ArtifactDeploymentManager manager; - private ModelArtifactHandler mockModelArtifactHandler; - private VnfCatalogArtifactHandler mockVnfCatalogArtifactHandler; + @Mock private ModelArtifactHandler modelArtifactHandlerMock; + @Mock private VnfCatalogArtifactHandler vnfCatalogArtifactHandlerMock; @BeforeEach public void setup() throws IOException { + MockitoAnnotations.openMocks(this); configProperties = new Properties(); configProperties.load(this.getClass().getClassLoader().getResourceAsStream(CONFIG_FILE)); - mockModelArtifactHandler = mock(ModelArtifactHandler.class); - mockVnfCatalogArtifactHandler = mock(VnfCatalogArtifactHandler.class); - - manager = new ArtifactDeploymentManager(new ModelLoaderConfig(configProperties, null)); - - ReflectionTestUtils.setField(manager, "modelArtifactHandler", mockModelArtifactHandler); - ReflectionTestUtils.setField(manager, "vnfCatalogArtifactHandler", mockVnfCatalogArtifactHandler); + ModelLoaderConfig modelLoaderConfig = new ModelLoaderConfig(configProperties, null); + manager = new ArtifactDeploymentManager(modelLoaderConfig, modelArtifactHandlerMock, vnfCatalogArtifactHandlerMock); } @AfterEach public void tearDown() { configProperties = null; - mockModelArtifactHandler = null; - mockVnfCatalogArtifactHandler = null; + modelArtifactHandlerMock = null; + vnfCatalogArtifactHandlerMock = null; manager = null; } @@ -95,18 +91,18 @@ public class TestArtifactDeploymentManager { List toscaArtifacts = setupTest(xml, data); List modelArtifacts = new BabelArtifactConverter().convertToModel(toscaArtifacts); - when(mockModelArtifactHandler.pushArtifacts(eq(modelArtifacts), eq(data.getDistributionID()), any(), any())) + when(modelArtifactHandlerMock.pushArtifacts(eq(modelArtifacts), eq(data.getDistributionID()), any(), any())) .thenReturn(false); assertThat(SHOULD_HAVE_RETURNED_FALSE, manager.deploy(data, modelArtifacts, new ArrayList<>()), is(false)); - Mockito.verify(mockModelArtifactHandler).pushArtifacts(eq(modelArtifacts), eq(data.getDistributionID()), any(), + Mockito.verify(modelArtifactHandlerMock).pushArtifacts(eq(modelArtifacts), eq(data.getDistributionID()), any(), any()); - Mockito.verify(mockVnfCatalogArtifactHandler, Mockito.never()).pushArtifacts(eq(modelArtifacts), + Mockito.verify(vnfCatalogArtifactHandlerMock, Mockito.never()).pushArtifacts(eq(modelArtifacts), eq(data.getDistributionID()), any(), any()); - Mockito.verify(mockModelArtifactHandler).rollback(eq(new ArrayList()), eq(data.getDistributionID()), + Mockito.verify(modelArtifactHandlerMock).rollback(eq(new ArrayList()), eq(data.getDistributionID()), any()); - Mockito.verify(mockVnfCatalogArtifactHandler, Mockito.never()).rollback(eq(new ArrayList()), + Mockito.verify(vnfCatalogArtifactHandlerMock, Mockito.never()).rollback(eq(new ArrayList()), eq(data.getDistributionID()), any()); } @@ -129,19 +125,19 @@ public class TestArtifactDeploymentManager { List catalogFiles = new ArrayList<>(); catalogFiles.add(new VnfCatalogArtifact("Some catalog content")); - when(mockModelArtifactHandler.pushArtifacts(any(), any(), any(), any())).thenReturn(true); - when(mockVnfCatalogArtifactHandler.pushArtifacts(eq(catalogFiles), eq(data.getDistributionID()), any(), any())) + when(modelArtifactHandlerMock.pushArtifacts(any(), any(), any(), any())).thenReturn(true); + when(vnfCatalogArtifactHandlerMock.pushArtifacts(eq(catalogFiles), eq(data.getDistributionID()), any(), any())) .thenReturn(false); assertThat(SHOULD_HAVE_RETURNED_FALSE, manager.deploy(data, new ArrayList<>(), catalogFiles), is(false)); - Mockito.verify(mockModelArtifactHandler).pushArtifacts(eq(new ArrayList()), + Mockito.verify(modelArtifactHandlerMock).pushArtifacts(eq(new ArrayList()), eq(data.getDistributionID()), any(), any()); - Mockito.verify(mockVnfCatalogArtifactHandler).pushArtifacts(eq(catalogFiles), eq(data.getDistributionID()), + Mockito.verify(vnfCatalogArtifactHandlerMock).pushArtifacts(eq(catalogFiles), eq(data.getDistributionID()), any(), any()); - Mockito.verify(mockModelArtifactHandler).rollback(eq(new ArrayList()), eq(data.getDistributionID()), + Mockito.verify(modelArtifactHandlerMock).rollback(eq(new ArrayList()), eq(data.getDistributionID()), any()); - Mockito.verify(mockVnfCatalogArtifactHandler).rollback(eq(new ArrayList()), + Mockito.verify(vnfCatalogArtifactHandlerMock).rollback(eq(new ArrayList()), eq(data.getDistributionID()), any()); } @@ -170,34 +166,34 @@ public class TestArtifactDeploymentManager { List catalogFiles = new ArrayList<>(); catalogFiles.add(new VnfCatalogArtifact("Some catalog content")); - when(mockVnfCatalogArtifactHandler.pushArtifacts(eq(catalogFiles), eq(data.getDistributionID()), any(), any())) + when(vnfCatalogArtifactHandlerMock.pushArtifacts(eq(catalogFiles), eq(data.getDistributionID()), any(), any())) .thenReturn(catalogsDeployed); - when(mockModelArtifactHandler.pushArtifacts(eq(modelArtifacts), eq(data.getDistributionID()), any(), any())) + when(modelArtifactHandlerMock.pushArtifacts(eq(modelArtifacts), eq(data.getDistributionID()), any(), any())) .thenReturn(modelsDeployed); assertThat(SHOULD_HAVE_RETURNED_FALSE, manager.deploy(data, modelArtifacts, catalogFiles), is(false)); // Catalog artifacts are only pushed if models are successful. - Mockito.verify(mockModelArtifactHandler).pushArtifacts(eq(modelArtifacts), eq(data.getDistributionID()), any(), + Mockito.verify(modelArtifactHandlerMock).pushArtifacts(eq(modelArtifacts), eq(data.getDistributionID()), any(), any()); if (modelsDeployed) { - Mockito.verify(mockVnfCatalogArtifactHandler).pushArtifacts(eq(catalogFiles), eq(data.getDistributionID()), + Mockito.verify(vnfCatalogArtifactHandlerMock).pushArtifacts(eq(catalogFiles), eq(data.getDistributionID()), any(), any()); } if (modelsDeployed && catalogsDeployed) { - Mockito.verify(mockModelArtifactHandler, Mockito.never()).rollback(any(), any(), any()); - Mockito.verify(mockVnfCatalogArtifactHandler, Mockito.never()).rollback(any(), any(), any()); + Mockito.verify(modelArtifactHandlerMock, Mockito.never()).rollback(any(), any(), any()); + Mockito.verify(vnfCatalogArtifactHandlerMock, Mockito.never()).rollback(any(), any(), any()); } else { if (modelsDeployed) { - Mockito.verify(mockModelArtifactHandler).rollback(eq(new ArrayList()), + Mockito.verify(modelArtifactHandlerMock).rollback(eq(new ArrayList()), eq(data.getDistributionID()), any()); - Mockito.verify(mockVnfCatalogArtifactHandler).rollback(eq(new ArrayList()), + Mockito.verify(vnfCatalogArtifactHandlerMock).rollback(eq(new ArrayList()), eq(data.getDistributionID()), any()); } else { - Mockito.verify(mockModelArtifactHandler).rollback(eq(new ArrayList()), + Mockito.verify(modelArtifactHandlerMock).rollback(eq(new ArrayList()), eq(data.getDistributionID()), any()); - Mockito.verify(mockVnfCatalogArtifactHandler, Mockito.never()).rollback(any(), any(), any()); + Mockito.verify(vnfCatalogArtifactHandlerMock, Mockito.never()).rollback(any(), any(), any()); } } } @@ -219,18 +215,18 @@ public class TestArtifactDeploymentManager { List catalogFiles = new ArrayList<>(); catalogFiles.add(new VnfCatalogArtifact("Some catalog content")); - when(mockVnfCatalogArtifactHandler.pushArtifacts(eq(catalogFiles), eq(data.getDistributionID()), any(), any())) + when(vnfCatalogArtifactHandlerMock.pushArtifacts(eq(catalogFiles), eq(data.getDistributionID()), any(), any())) .thenReturn(true); - when(mockModelArtifactHandler.pushArtifacts(eq(modelArtifacts), eq(data.getDistributionID()), any(), any())) + when(modelArtifactHandlerMock.pushArtifacts(eq(modelArtifacts), eq(data.getDistributionID()), any(), any())) .thenReturn(true); assertThat(manager.deploy(data, modelArtifacts, catalogFiles), is(true)); - Mockito.verify(mockVnfCatalogArtifactHandler).pushArtifacts(eq(catalogFiles), eq(data.getDistributionID()), + Mockito.verify(vnfCatalogArtifactHandlerMock).pushArtifacts(eq(catalogFiles), eq(data.getDistributionID()), any(), any()); - Mockito.verify(mockModelArtifactHandler).pushArtifacts(eq(modelArtifacts), eq(data.getDistributionID()), any(), + Mockito.verify(modelArtifactHandlerMock).pushArtifacts(eq(modelArtifacts), eq(data.getDistributionID()), any(), any()); - Mockito.verify(mockModelArtifactHandler, Mockito.never()).rollback(any(), any(), any()); - Mockito.verify(mockVnfCatalogArtifactHandler, Mockito.never()).rollback(any(), any(), any()); + Mockito.verify(modelArtifactHandlerMock, Mockito.never()).rollback(any(), any(), any()); + Mockito.verify(vnfCatalogArtifactHandlerMock, Mockito.never()).rollback(any(), any(), any()); } } diff --git a/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDownloadManager.java b/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDownloadManager.java index 561791b..c09eff5 100644 --- a/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDownloadManager.java +++ b/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDownloadManager.java @@ -44,15 +44,17 @@ import org.hamcrest.collection.IsEmptyCollection; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; import org.onap.aai.babel.service.data.BabelArtifact; import org.onap.aai.modelloader.config.ModelLoaderConfig; import org.onap.aai.modelloader.entity.Artifact; import org.onap.aai.modelloader.entity.model.BabelArtifactParsingException; +import org.onap.aai.modelloader.extraction.VnfCatalogExtractor; import org.onap.aai.modelloader.restclient.BabelServiceClient; import org.onap.aai.modelloader.restclient.BabelServiceClientException; import org.onap.aai.modelloader.service.BabelServiceClientFactory; -import org.onap.aai.modelloader.service.HttpsBabelServiceClientFactory; import org.onap.aai.modelloader.util.ArtifactTestUtils; import org.onap.sdc.api.IDistributionClient; import org.onap.sdc.api.notification.IArtifactInfo; @@ -60,7 +62,6 @@ import org.onap.sdc.api.notification.INotificationData; import org.onap.sdc.api.results.IDistributionClientDownloadResult; import org.onap.sdc.impl.DistributionClientDownloadResultImpl; import org.onap.sdc.utils.DistributionActionResultEnum; -import org.springframework.test.util.ReflectionTestUtils; /** * Tests {@link ArtifactDownloadManager}. @@ -68,28 +69,23 @@ import org.springframework.test.util.ReflectionTestUtils; public class TestArtifactDownloadManager { private ArtifactDownloadManager downloadManager; - private BabelServiceClient mockBabelClient; - private IDistributionClient mockDistributionClient; - private NotificationPublisher mockNotificationPublisher; - private BabelArtifactConverter mockBabelArtifactConverter; - private BabelServiceClientFactory mockClientFactory; + @Mock private BabelServiceClient mockBabelClient; + @Mock private IDistributionClient mockDistributionClient; + @Mock private NotificationPublisher mockNotificationPublisher; + @Mock private BabelArtifactConverter mockBabelArtifactConverter; + @Mock private BabelServiceClientFactory mockClientFactory; + private VnfCatalogExtractor vnfCatalogExtractor; @BeforeEach public void setup() throws Exception { - mockBabelClient = mock(BabelServiceClient.class); - mockDistributionClient = mock(IDistributionClient.class); - mockNotificationPublisher = mock(NotificationPublisher.class); - mockBabelArtifactConverter = mock(BabelArtifactConverter.class); - mockClientFactory = mock(HttpsBabelServiceClientFactory.class); + MockitoAnnotations.openMocks(this); + vnfCatalogExtractor = new VnfCatalogExtractor(); when(mockClientFactory.create(any())).thenReturn(mockBabelClient); Properties configProperties = new Properties(); configProperties.load(this.getClass().getClassLoader().getResourceAsStream("model-loader.properties")); downloadManager = new ArtifactDownloadManager(mockDistributionClient, - new ModelLoaderConfig(configProperties, "."), mockClientFactory); - - ReflectionTestUtils.setField(downloadManager, "notificationPublisher", mockNotificationPublisher); - ReflectionTestUtils.setField(downloadManager, "babelArtifactConverter", mockBabelArtifactConverter); + new ModelLoaderConfig(configProperties, "."), mockClientFactory, mockBabelArtifactConverter, mockNotificationPublisher, vnfCatalogExtractor); } @AfterEach diff --git a/src/test/java/org/onap/aai/modelloader/notification/TestEventCallback.java b/src/test/java/org/onap/aai/modelloader/notification/TestEventCallback.java index d9245d9..1073a61 100644 --- a/src/test/java/org/onap/aai/modelloader/notification/TestEventCallback.java +++ b/src/test/java/org/onap/aai/modelloader/notification/TestEventCallback.java @@ -21,7 +21,6 @@ package org.onap.aai.modelloader.notification; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -32,14 +31,14 @@ import java.util.Properties; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mock; import org.mockito.Mockito; -import org.onap.aai.modelloader.config.ModelLoaderConfig; +import org.mockito.MockitoAnnotations; import org.onap.aai.modelloader.entity.model.BabelArtifactParsingException; import org.onap.aai.modelloader.fixture.NotificationDataFixtureBuilder; import org.onap.aai.modelloader.service.ArtifactDeploymentManager; import org.onap.sdc.api.IDistributionClient; import org.onap.sdc.api.notification.INotificationData; -import org.springframework.test.util.ReflectionTestUtils; /** * Tests {@link EventCallback}. @@ -48,36 +47,25 @@ public class TestEventCallback { private static final String CONFIG_FILE = "model-loader.properties"; - private ModelLoaderConfig config; private Properties configProperties; private EventCallback eventCallback; - private ArtifactDeploymentManager mockArtifactDeploymentManager; - private ArtifactDownloadManager mockArtifactDownloadManager; - private IDistributionClient mockDistributionClient; - private NotificationPublisher mockNotificationPublisher; + @Mock private ArtifactDeploymentManager mockArtifactDeploymentManager; + @Mock private ArtifactDownloadManager mockArtifactDownloadManager; + @Mock private IDistributionClient mockDistributionClient; + @Mock private NotificationPublisher mockNotificationPublisher; @BeforeEach public void setup() throws IOException { + MockitoAnnotations.openMocks(this); configProperties = new Properties(); configProperties.load(this.getClass().getClassLoader().getResourceAsStream(CONFIG_FILE)); - config = new ModelLoaderConfig(configProperties, null); - mockArtifactDeploymentManager = mock(ArtifactDeploymentManager.class); - mockArtifactDownloadManager = mock(ArtifactDownloadManager.class); - mockDistributionClient = mock(IDistributionClient.class); - mockNotificationPublisher = mock(NotificationPublisher.class); - - eventCallback = new EventCallback(mockDistributionClient, config, null); - - ReflectionTestUtils.setField(eventCallback, "artifactDeploymentManager", mockArtifactDeploymentManager); - ReflectionTestUtils.setField(eventCallback, "artifactDownloadManager", mockArtifactDownloadManager); - ReflectionTestUtils.setField(eventCallback, "notificationPublisher", mockNotificationPublisher); + eventCallback = new EventCallback(mockDistributionClient, mockArtifactDeploymentManager, mockArtifactDownloadManager, mockNotificationPublisher); } @AfterEach public void tearDown() { - config = null; configProperties = null; eventCallback = null; mockArtifactDeploymentManager = null; diff --git a/src/test/java/org/onap/aai/modelloader/notification/TestNotificationPublisher.java b/src/test/java/org/onap/aai/modelloader/notification/TestNotificationPublisher.java index edf50a8..c4aa932 100644 --- a/src/test/java/org/onap/aai/modelloader/notification/TestNotificationPublisher.java +++ b/src/test/java/org/onap/aai/modelloader/notification/TestNotificationPublisher.java @@ -63,7 +63,7 @@ public class TestNotificationPublisher { @BeforeEach public void setupMocks() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); when(client.getConfiguration()).thenReturn(config); when(client.sendDownloadStatus(any())).thenReturn(clientResult); when(client.sendComponentDoneStatus(any())).thenReturn(clientResult); diff --git a/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderService.java b/src/test/java/org/onap/aai/modelloader/service/TestModelController.java similarity index 51% rename from src/test/java/org/onap/aai/modelloader/service/TestModelLoaderService.java rename to src/test/java/org/onap/aai/modelloader/service/TestModelController.java index e58716c..9106342 100644 --- a/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderService.java +++ b/src/test/java/org/onap/aai/modelloader/service/TestModelController.java @@ -22,16 +22,30 @@ package org.onap.aai.modelloader.service; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.io.IOException; import java.util.Base64; +import java.util.Collections; import javax.ws.rs.core.Response; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.onap.aai.modelloader.config.BeanConfig; +import org.mockito.Mock; +import org.onap.aai.modelloader.config.ModelLoaderConfig; +import org.onap.aai.modelloader.extraction.VnfCatalogExtractor; +import org.onap.aai.modelloader.notification.ArtifactDownloadManager; +import org.onap.aai.modelloader.notification.BabelArtifactConverter; +import org.onap.aai.modelloader.notification.EventCallback; +import org.onap.aai.modelloader.notification.NotificationPublisher; +import org.onap.aai.modelloader.restclient.BabelServiceClient; +import org.onap.aai.modelloader.restclient.BabelServiceClientException; import org.onap.aai.modelloader.util.ArtifactTestUtils; +import org.onap.sdc.api.IDistributionClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.TestPropertySource; @@ -40,41 +54,59 @@ import org.springframework.test.context.TestPropertySource; * Tests for the ModelLoaderService class. * */ -@SpringBootTest(classes = {BeanConfig.class, ModelController.class, MockBabelServiceClientFactory.class}) +@SpringBootTest @TestPropertySource(properties = {"CONFIG_HOME=src/test/resources",}) -public class TestModelLoaderService { +public class TestModelController { - @Autowired - private ModelController service; + @Autowired IDistributionClient iDistributionClient; + @Autowired ModelLoaderConfig modelLoaderConfig; + @Autowired EventCallback eventCallback; + @Autowired ArtifactDeploymentManager artifactDeploymentManager; + @Autowired BabelArtifactConverter babelArtifactConverter; + @Autowired NotificationPublisher notificationPublisher; + @Autowired VnfCatalogExtractor vnfCatalogExtractor; + + @Mock BabelServiceClientFactory clientFactory; + @Mock BabelServiceClient babelServiceClient; + + private ModelController modelController; + + @BeforeEach + public void init() throws BabelServiceClientException { + when(clientFactory.create(any())).thenReturn(babelServiceClient); + when(babelServiceClient.postArtifact(any(), any(), any(), any())).thenReturn(Collections.emptyList()); + ArtifactDownloadManager artifactDownloadManager = new ArtifactDownloadManager(iDistributionClient, modelLoaderConfig, clientFactory, babelArtifactConverter, notificationPublisher, vnfCatalogExtractor); + this.modelController = new ModelController(iDistributionClient, modelLoaderConfig, eventCallback, artifactDeploymentManager, artifactDownloadManager); + } @AfterEach public void shutdown() { - service.preShutdownOperations(); + modelController.preShutdownOperations(); } @Test public void testLoadModel() { - Response response = service.loadModel(""); + Response response = modelController.loadModel(""); assertThat(response.getStatus(), is(Response.Status.OK.getStatusCode())); } @Test public void testSaveModel() { - Response response = service.saveModel("", ""); + Response response = modelController.saveModel("", ""); assertThat(response.getStatus(), is(Response.Status.OK.getStatusCode())); } @Test public void testIngestModel() throws IOException { byte[] csarPayload = new ArtifactTestUtils().loadResource("compressedArtifacts/service-VscpaasTest-csar.csar"); - Response response = service.ingestModel("model-name", "", Base64.getEncoder().encodeToString(csarPayload)); + Response response = modelController.ingestModel("model-name", "", Base64.getEncoder().encodeToString(csarPayload)); assertThat(response.getStatus(), is(Response.Status.OK.getStatusCode())); } @Test public void testIngestModelMissingName() throws IOException { byte[] csarPayload = new ArtifactTestUtils().loadResource("compressedArtifacts/service-VscpaasTest-csar.csar"); - Response response = service.ingestModel("", "", Base64.getEncoder().encodeToString(csarPayload)); + Response response = modelController.ingestModel("", "", Base64.getEncoder().encodeToString(csarPayload)); assertThat(response.getStatus(), is(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode())); } diff --git a/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderServiceWithSdc.java b/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderServiceWithSdc.java index 0b68cc0..a8501be 100644 --- a/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderServiceWithSdc.java +++ b/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderServiceWithSdc.java @@ -30,7 +30,6 @@ import javax.ws.rs.core.Response; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import org.onap.aai.modelloader.config.BeanConfig; import org.onap.aai.modelloader.util.ArtifactTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -40,7 +39,7 @@ import org.springframework.test.context.TestPropertySource; * Tests for the ModelLoaderService class. * */ -@SpringBootTest(classes = {BeanConfig.class, ModelController.class, HttpsBabelServiceClientFactory.class}) +@SpringBootTest @TestPropertySource(properties = {"CONFIG_HOME=src/test/resources",}) public class TestModelLoaderServiceWithSdc { -- 2.16.6 From 598c2469a004c50a1b29882e02e2fab7a8407d8b Mon Sep 17 00:00:00 2001 From: Fiete Ostkamp Date: Thu, 28 Dec 2023 16:17:47 +0100 Subject: [PATCH 10/16] Make rest-client request timeout in model-loader configurable - this reduces the test execution time, when test cases run into timouts Issue-ID: AAI-3703 Change-Id: Ie683f55e5ce8d59b10d2788aea6933854dcb7e99 Signed-off-by: Fiete Ostkamp --- .../aai/modelloader/config/ModelLoaderConfig.java | 14 +++++++++++++- .../aai/modelloader/restclient/AaiRestClient.java | 19 +++++++------------ .../restclient/HttpsBabelServiceClient.java | 2 ++ .../modelloader/restclient/TestAaiServiceClient.java | 8 +++++--- .../restclient/TestBabelServiceClient.java | 10 +++++++--- 5 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/onap/aai/modelloader/config/ModelLoaderConfig.java b/src/main/java/org/onap/aai/modelloader/config/ModelLoaderConfig.java index a49288d..7da90d9 100644 --- a/src/main/java/org/onap/aai/modelloader/config/ModelLoaderConfig.java +++ b/src/main/java/org/onap/aai/modelloader/config/ModelLoaderConfig.java @@ -27,12 +27,12 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.Properties; import org.apache.commons.lang3.StringUtils; import org.eclipse.jetty.util.security.Password; import org.onap.sdc.api.consumer.IConfiguration; - /** * Properties for the Model Loader * @@ -97,6 +97,8 @@ public class ModelLoaderConfig implements IConfiguration { protected static final String PROP_DEBUG_INGEST_SIMULATOR = PREFIX_DEBUG + "INGEST_SIMULATOR"; protected static final String FILESEP = (System.getProperty("file.separator") == null) ? "/" : System.getProperty("file.separator"); + protected static final String PROP_AAI_CLIENT_CONNECT_TIMEOUT_MS = PREFIX_AAI + "RESTCLIENT_CONNECT_TIMEOUT"; + protected static final String PROP_AAI_CLIENT_READ_TIMEOUT_MS = PREFIX_AAI + "RESTCLIENT_READ_TIMEOUT"; private static String configHome; private Properties modelLoaderProperties = null; @@ -401,4 +403,14 @@ public class ModelLoaderConfig implements IConfiguration { } } + public int getClientConnectTimeoutMs() { + String connectTimeout = Optional.ofNullable(get(PROP_AAI_CLIENT_CONNECT_TIMEOUT_MS)).orElse("120000"); + return Integer.parseInt(connectTimeout); + } + + public int getClientReadTimeoutMs() { + String connectTimeout = Optional.ofNullable(get(PROP_AAI_CLIENT_READ_TIMEOUT_MS)).orElse("120000"); + return Integer.parseInt(connectTimeout); + } + } diff --git a/src/main/java/org/onap/aai/modelloader/restclient/AaiRestClient.java b/src/main/java/org/onap/aai/modelloader/restclient/AaiRestClient.java index 29c0c70..40aeacc 100644 --- a/src/main/java/org/onap/aai/modelloader/restclient/AaiRestClient.java +++ b/src/main/java/org/onap/aai/modelloader/restclient/AaiRestClient.java @@ -156,24 +156,19 @@ public class AaiRestClient { private RestClient setupClient() { RestClient restClient = new RestClient(); + restClient.validateServerHostname(false) + .validateServerCertChain(false) + .connectTimeoutMs(config.getClientConnectTimeoutMs()) + .readTimeoutMs(config.getClientReadTimeoutMs()); //Use certs only if SSL is enabled if (config.useHttpsWithAAI()) {// @formatter:off - restClient.validateServerHostname(false) - .validateServerCertChain(false) - .clientCertFile(config.getAaiKeyStorePath()) - .clientCertPassword(config.getAaiKeyStorePassword()) - .connectTimeoutMs(120000) - .readTimeoutMs(120000); + restClient + .clientCertFile(config.getAaiKeyStorePath()) + .clientCertPassword(config.getAaiKeyStorePassword()); // @formatter:on } - else { - restClient.validateServerHostname(false) - .validateServerCertChain(false) - .connectTimeoutMs(120000) - .readTimeoutMs(120000); - } if (useBasicAuth()) { restClient.authenticationMode(RestAuthenticationMode.SSL_BASIC); diff --git a/src/main/java/org/onap/aai/modelloader/restclient/HttpsBabelServiceClient.java b/src/main/java/org/onap/aai/modelloader/restclient/HttpsBabelServiceClient.java index 88967b2..c76996f 100644 --- a/src/main/java/org/onap/aai/modelloader/restclient/HttpsBabelServiceClient.java +++ b/src/main/java/org/onap/aai/modelloader/restclient/HttpsBabelServiceClient.java @@ -130,6 +130,8 @@ public class HttpsBabelServiceClient implements BabelServiceClient { } client = Client.create(new DefaultClientConfig()); + client.setConnectTimeout(config.getClientConnectTimeoutMs()); + client.setReadTimeout(config.getClientReadTimeoutMs()); logger.debug(ModelLoaderMsgs.DISTRIBUTION_EVENT, "Jersey client created"); } diff --git a/src/test/java/org/onap/aai/modelloader/restclient/TestAaiServiceClient.java b/src/test/java/org/onap/aai/modelloader/restclient/TestAaiServiceClient.java index fd65383..18753b1 100644 --- a/src/test/java/org/onap/aai/modelloader/restclient/TestAaiServiceClient.java +++ b/src/test/java/org/onap/aai/modelloader/restclient/TestAaiServiceClient.java @@ -53,12 +53,14 @@ public class TestAaiServiceClient { @BeforeEach public void startJetty() throws Exception { - server = new Server(8080); + server = new Server(0); server.setHandler(getMockHandler()); server.start(); Properties props = new Properties(); props.put("ml.aai.KEYSTORE_PASSWORD", "2244"); + props.put("ml.aai.RESTCLIENT_CONNECT_TIMEOUT", "3000"); + props.put("ml.aai.RESTCLIENT_READ_TIMEOUT", "3000"); ModelLoaderConfig config = new ModelLoaderConfig(props, "."); aaiClient = new AaiRestClient(config); } @@ -78,11 +80,11 @@ public class TestAaiServiceClient { @Test public void testOperations() { - String url = "http://localhost:8080"; + String url = server.getURI().toString(); String transId = ""; MediaType mediaType = MediaType.APPLICATION_JSON_TYPE; aaiClient.getResource(url, "", mediaType); - aaiClient.deleteResource("http://localhost", transId, ""); + aaiClient.deleteResource(url, "", transId); aaiClient.getAndDeleteResource(url, transId); aaiClient.postResource(url, "", transId, mediaType); aaiClient.putResource(url, "", transId, mediaType); diff --git a/src/test/java/org/onap/aai/modelloader/restclient/TestBabelServiceClient.java b/src/test/java/org/onap/aai/modelloader/restclient/TestBabelServiceClient.java index c653244..19e35ca 100644 --- a/src/test/java/org/onap/aai/modelloader/restclient/TestBabelServiceClient.java +++ b/src/test/java/org/onap/aai/modelloader/restclient/TestBabelServiceClient.java @@ -69,7 +69,7 @@ public class TestBabelServiceClient { response.add(new BabelArtifact("", null, "")); responseBody = new Gson().toJson(response); - server = new Server(8080); + server = new Server(0); server.setHandler(getMockHandler()); server.start(); } @@ -81,13 +81,14 @@ public class TestBabelServiceClient { @Test public void testRestClient() throws BabelServiceClientException, IOException, URISyntaxException { + String url = server.getURI().toString(); Properties configProperties = new Properties(); configProperties.put("ml.babel.KEYSTORE_PASSWORD", "OBF:1vn21ugu1saj1v9i1v941sar1ugw1vo0"); configProperties.put("ml.babel.KEYSTORE_FILE", "src/test/resources/auth/aai-client-dummy.p12"); configProperties.put("ml.babel.TRUSTSTORE_PASSWORD", "OBF:1vn21ugu1saj1v9i1v941sar1ugw1vo0"); // In a real deployment this would be a different file (to the client keystore) configProperties.put("ml.babel.TRUSTSTORE_FILE", "src/test/resources/auth/aai-client-dummy.p12"); - configProperties.put("ml.babel.BASE_URL", "http://localhost:8080/"); + configProperties.put("ml.babel.BASE_URL", url); configProperties.put("ml.babel.GENERATE_ARTIFACTS_URL", "generate"); BabelServiceClient client = new HttpsBabelServiceClientFactory().create(new ModelLoaderConfig(configProperties, ".")); @@ -99,10 +100,13 @@ public class TestBabelServiceClient { @Test public void testRestClientHttp() throws BabelServiceClientException, IOException, URISyntaxException { + String url = server.getURI().toString(); Properties configProperties = new Properties(); configProperties.put("ml.babel.USE_HTTPS", "false"); - configProperties.put("ml.babel.BASE_URL", "http://localhost:8080/"); + configProperties.put("ml.babel.BASE_URL", url); configProperties.put("ml.babel.GENERATE_ARTIFACTS_URL", "generate"); + configProperties.put("ml.aai.RESTCLIENT_CONNECT_TIMEOUT", "3000"); + configProperties.put("ml.aai.RESTCLIENT_READ_TIMEOUT", "3000"); BabelServiceClient client = new HttpsBabelServiceClientFactory().create(new ModelLoaderConfig(configProperties, ".")); List result = -- 2.16.6 From f9999359c898143d0fb9a4a62063cbc51a40e25e Mon Sep 17 00:00:00 2001 From: Fiete Ostkamp Date: Fri, 5 Apr 2024 11:38:01 +0200 Subject: [PATCH 11/16] Model distribution fails with model-loader 1.13.5 - move sdc-distribution-client instantiation out of the ModelController into a separate class - add integration test with embedded kafka (not fully implemented) Issue-ID: AAI-3818 Change-Id: I0b5dd118d9145372ddf123319b58829d0ef9275a Signed-off-by: Fiete Ostkamp --- pom.xml | 18 ++++- .../config/DistributionClientStartupConfig.java | 87 ++++++++++++++++++++++ .../aai/modelloader/service/ModelController.java | 8 ++ .../distribution/EventCallbackAspect.java | 49 ++++++++++++ .../distribution/NotificationIntegrationTest.java | 66 ++++++++++++++++ .../entity/model/TestModelArtifactHandler.java | 2 +- .../notification/TestArtifactDownloadManager.java | 1 - .../restclient/TestBabelServiceClient.java | 2 + .../modelloader/service/TestModelController.java | 1 - src/test/resources/application.properties | 10 +++ src/test/resources/model-loader.properties | 4 +- 11 files changed, 242 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/onap/aai/modelloader/config/DistributionClientStartupConfig.java create mode 100644 src/test/java/org/onap/aai/modelloader/distribution/EventCallbackAspect.java create mode 100644 src/test/java/org/onap/aai/modelloader/distribution/NotificationIntegrationTest.java create mode 100644 src/test/resources/application.properties diff --git a/pom.xml b/pom.xml index 61a4ec2..644e408 100644 --- a/pom.xml +++ b/pom.xml @@ -239,10 +239,26 @@ + + org.springframework.boot + spring-boot-starter-aop + test + org.apache.kafka kafka-clients - 3.3.1 + + + + org.springframework.kafka + spring-kafka + + + + org.springframework.kafka + spring-kafka-test + + test org.onap.aai diff --git a/src/main/java/org/onap/aai/modelloader/config/DistributionClientStartupConfig.java b/src/main/java/org/onap/aai/modelloader/config/DistributionClientStartupConfig.java new file mode 100644 index 0000000..84c79f2 --- /dev/null +++ b/src/main/java/org/onap/aai/modelloader/config/DistributionClientStartupConfig.java @@ -0,0 +1,87 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2024 Deutsche Telekom AG Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +package org.onap.aai.modelloader.config; + +import java.util.Date; +import java.util.Timer; +import java.util.TimerTask; + +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.modelloader.notification.EventCallback; +import org.onap.aai.modelloader.service.ModelLoaderMsgs; +import org.onap.aai.modelloader.service.SdcConnectionJob; +import org.onap.sdc.api.IDistributionClient; +import org.onap.sdc.api.results.IDistributionClientResult; +import org.onap.sdc.utils.DistributionActionResultEnum; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +@Component +@ConditionalOnProperty(value = "ml.distribution.connection.enabled", havingValue = "true", matchIfMissing = true) +public class DistributionClientStartupConfig { + + private static final Logger logger = LoggerFactory.getInstance().getLogger(DistributionClientStartupConfig.class); + + private final IDistributionClient client; + private final ModelLoaderConfig config; + private final EventCallback eventCallback; + + public DistributionClientStartupConfig(IDistributionClient client, ModelLoaderConfig config, + EventCallback eventCallback) { + this.client = client; + this.config = config; + this.eventCallback = eventCallback; + } + + @EventListener(ApplicationReadyEvent.class) + protected void initSdcClient() { + // Initialize distribution client + logger.debug(ModelLoaderMsgs.INITIALIZING, "Initializing distribution client..."); + IDistributionClientResult initResult = client.init(config, eventCallback); + + if (initResult.getDistributionActionResult() == DistributionActionResultEnum.SUCCESS) { + // Start distribution client + logger.debug(ModelLoaderMsgs.INITIALIZING, "Starting distribution client..."); + IDistributionClientResult startResult = client.start(); + if (startResult.getDistributionActionResult() == DistributionActionResultEnum.SUCCESS) { + logger.info(ModelLoaderMsgs.INITIALIZING, "Connection to SDC established"); + } else { + String errorMsg = "Failed to start distribution client: " + startResult.getDistributionMessageResult(); + logger.error(ModelLoaderMsgs.ASDC_CONNECTION_ERROR, errorMsg); + + // Kick off a timer to retry the SDC connection + Timer timer = new Timer(); + TimerTask task = new SdcConnectionJob(client, config, eventCallback, timer); + timer.schedule(task, new Date(), 60000); + } + } else { + String errorMsg = "Failed to initialize distribution client: " + initResult.getDistributionMessageResult(); + logger.error(ModelLoaderMsgs.ASDC_CONNECTION_ERROR, errorMsg); + + // Kick off a timer to retry the SDC connection + Timer timer = new Timer(); + TimerTask task = new SdcConnectionJob(client, config, eventCallback, timer); + timer.schedule(task, new Date(), 60000); + } + } +} diff --git a/src/main/java/org/onap/aai/modelloader/service/ModelController.java b/src/main/java/org/onap/aai/modelloader/service/ModelController.java index 0921982..5c784b2 100644 --- a/src/main/java/org/onap/aai/modelloader/service/ModelController.java +++ b/src/main/java/org/onap/aai/modelloader/service/ModelController.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Timer; import java.util.TimerTask; +import javax.annotation.PostConstruct; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @@ -71,6 +72,13 @@ public class ModelController implements ModelLoaderInterface { this.artifactDownloadManager = artifactDownloadManager; } + @PostConstruct + protected void start() { + if (!config.getASDCConnectionDisabled()) { + initSdcClient(); + } + } + /** * Responsible for stopping the connection to the distribution client before the resource is destroyed. */ diff --git a/src/test/java/org/onap/aai/modelloader/distribution/EventCallbackAspect.java b/src/test/java/org/onap/aai/modelloader/distribution/EventCallbackAspect.java new file mode 100644 index 0000000..16f311b --- /dev/null +++ b/src/test/java/org/onap/aai/modelloader/distribution/EventCallbackAspect.java @@ -0,0 +1,49 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2024 Deutsche Telekom AG Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +package org.onap.aai.modelloader.distribution; + +import java.util.concurrent.CountDownLatch; + +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; + +import lombok.Getter; +import lombok.Setter; + +@Aspect +@Getter +@Setter +@Component +/** + * Aspect to asynchronously wait for the EventCallback being called after a distribution event from sdc was published + */ +public class EventCallbackAspect { + + private CountDownLatch countDownLatch; + + @After( + "execution(* org.onap.aai.modelloader.notification.EventCallback.activateCallback(..))") + private void afterEventCallbackCalled() { + if (countDownLatch != null) { + countDownLatch.countDown(); + } + } +} \ No newline at end of file diff --git a/src/test/java/org/onap/aai/modelloader/distribution/NotificationIntegrationTest.java b/src/test/java/org/onap/aai/modelloader/distribution/NotificationIntegrationTest.java new file mode 100644 index 0000000..2645871 --- /dev/null +++ b/src/test/java/org/onap/aai/modelloader/distribution/NotificationIntegrationTest.java @@ -0,0 +1,66 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2024 Deutsche Telekom AG Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +package org.onap.aai.modelloader.distribution; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.test.context.EmbeddedKafka; +import org.springframework.test.annotation.DirtiesContext; + +@DirtiesContext +@SpringBootTest(properties = { "model-loader.sdc.connection.enabled=true"}) +@EmbeddedKafka(partitions = 1, brokerProperties = { "listeners=PLAINTEXT://localhost:9092", "port=9092" }) +public class NotificationIntegrationTest { + + @Autowired EventCallbackAspect eventCallbackAspect; + + @Autowired + private KafkaTemplate kafkaTemplate; + + @Value("${test.topic}") + private String topic; + + @Test + @Disabled("This test is not yet implemented") + public void thatActivateCallbackIsCalled() + throws Exception { + String data = "Smth"; + + // TODO: send distribution event here + kafkaTemplate.send(topic, data); + + // TODO: mock distribution client requests to /sdc/v1/artifactTypes + // TODO: mock distribution client requests to /sdc/v1/distributionKafkaData + + // aspect will wait for EventCallback.activateCallback to be called + eventCallbackAspect.setCountDownLatch(new CountDownLatch(1)); + boolean callbackCalled = eventCallbackAspect.getCountDownLatch().await(10, TimeUnit.SECONDS); + assertTrue(callbackCalled); + } +} diff --git a/src/test/java/org/onap/aai/modelloader/entity/model/TestModelArtifactHandler.java b/src/test/java/org/onap/aai/modelloader/entity/model/TestModelArtifactHandler.java index fac50cb..177a8d2 100644 --- a/src/test/java/org/onap/aai/modelloader/entity/model/TestModelArtifactHandler.java +++ b/src/test/java/org/onap/aai/modelloader/entity/model/TestModelArtifactHandler.java @@ -57,7 +57,7 @@ public class TestModelArtifactHandler { @BeforeEach public void setupMocks() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); } @Test diff --git a/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDownloadManager.java b/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDownloadManager.java index c09eff5..48b9a66 100644 --- a/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDownloadManager.java +++ b/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDownloadManager.java @@ -27,7 +27,6 @@ import static org.hamcrest.collection.IsEmptyCollection.empty; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.onap.aai.modelloader.fixture.NotificationDataFixtureBuilder.getNotificationDataWithInvalidType; import static org.onap.aai.modelloader.fixture.NotificationDataFixtureBuilder.getNotificationDataWithModelQuerySpec; diff --git a/src/test/java/org/onap/aai/modelloader/restclient/TestBabelServiceClient.java b/src/test/java/org/onap/aai/modelloader/restclient/TestBabelServiceClient.java index 19e35ca..77e1594 100644 --- a/src/test/java/org/onap/aai/modelloader/restclient/TestBabelServiceClient.java +++ b/src/test/java/org/onap/aai/modelloader/restclient/TestBabelServiceClient.java @@ -90,6 +90,8 @@ public class TestBabelServiceClient { configProperties.put("ml.babel.TRUSTSTORE_FILE", "src/test/resources/auth/aai-client-dummy.p12"); configProperties.put("ml.babel.BASE_URL", url); configProperties.put("ml.babel.GENERATE_ARTIFACTS_URL", "generate"); + configProperties.put("ml.aai.RESTCLIENT_CONNECT_TIMEOUT", "12000"); + configProperties.put("ml.aai.RESTCLIENT_READ_TIMEOUT", "12000"); BabelServiceClient client = new HttpsBabelServiceClientFactory().create(new ModelLoaderConfig(configProperties, ".")); List result = diff --git a/src/test/java/org/onap/aai/modelloader/service/TestModelController.java b/src/test/java/org/onap/aai/modelloader/service/TestModelController.java index 9106342..82f7203 100644 --- a/src/test/java/org/onap/aai/modelloader/service/TestModelController.java +++ b/src/test/java/org/onap/aai/modelloader/service/TestModelController.java @@ -23,7 +23,6 @@ package org.onap.aai.modelloader.service; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.io.IOException; diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 0000000..96816ce --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,10 @@ +CONFIG_HOME=src/test/resources +spring.kafka.consumer.auto-offset-reset=earliest +spring.kafka.consumer.group-id=aai +spring.kafka.consumer.client-id=aai-model-loader + +test.topic=SDC-DISTR-NOTIF-TOPIC + +spring.sleuth.enabled=false + +ml.distribution.connection.enabled=false # avoid having the distribution client running in the background (requires active kafka) \ No newline at end of file diff --git a/src/test/resources/model-loader.properties b/src/test/resources/model-loader.properties index 51a38c5..4c24679 100644 --- a/src/test/resources/model-loader.properties +++ b/src/test/resources/model-loader.properties @@ -7,8 +7,8 @@ ml.distribution.ENVIRONMENT_NAME=env ml.distribution.KEYSTORE_PASSWORD= ml.distribution.KEYSTORE_FILE= ml.distribution.PASSWORD=Aa123456 -ml.distribution.POLLING_INTERVAL=30 -ml.distribution.POLLING_TIMEOUT=20 +ml.distribution.POLLING_INTERVAL=5 +ml.distribution.POLLING_TIMEOUT=3 ml.distribution.USER=ci ml.distribution.ARTIFACT_TYPES=MODEL_QUERY_SPEC,TOSCA_CSAR -- 2.16.6 From e820afb80b830453f667280d94f6e86279ad4f2c Mon Sep 17 00:00:00 2001 From: Fiete Ostkamp Date: Thu, 11 Apr 2024 08:19:19 +0200 Subject: [PATCH 12/16] sdc-distribution-client is registered twice in model-loader - registration of client was moved into separate config class in last change - however registration in ModelController was not removed Issue-ID: AAI-3824 Change-Id: Icad4211fb245d84fd369c75dd48de7f9a1ec1b20 Signed-off-by: Fiete Ostkamp --- .../aai/modelloader/service/ModelController.java | 64 +--------------------- .../modelloader/service/TestModelController.java | 10 +--- .../service/TestModelLoaderServiceWithSdc.java | 10 +--- 3 files changed, 4 insertions(+), 80 deletions(-) diff --git a/src/main/java/org/onap/aai/modelloader/service/ModelController.java b/src/main/java/org/onap/aai/modelloader/service/ModelController.java index 5c784b2..41a7c86 100644 --- a/src/main/java/org/onap/aai/modelloader/service/ModelController.java +++ b/src/main/java/org/onap/aai/modelloader/service/ModelController.java @@ -23,12 +23,8 @@ package org.onap.aai.modelloader.service; import java.io.IOException; import java.util.ArrayList; import java.util.Base64; -import java.util.Date; import java.util.List; -import java.util.Timer; -import java.util.TimerTask; -import javax.annotation.PostConstruct; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @@ -37,13 +33,10 @@ import org.onap.aai.cl.eelf.LoggerFactory; import org.onap.aai.modelloader.config.ModelLoaderConfig; import org.onap.aai.modelloader.entity.Artifact; import org.onap.aai.modelloader.notification.ArtifactDownloadManager; -import org.onap.aai.modelloader.notification.EventCallback; import org.onap.aai.modelloader.notification.NotificationDataImpl; import org.onap.aai.modelloader.notification.NotificationPublisher; import org.onap.sdc.api.IDistributionClient; import org.onap.sdc.api.notification.IArtifactInfo; -import org.onap.sdc.api.results.IDistributionClientResult; -import org.onap.sdc.utils.DistributionActionResultEnum; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -60,71 +53,16 @@ public class ModelController implements ModelLoaderInterface { private final IDistributionClient client; private final ModelLoaderConfig config; - private final EventCallback eventCallback; private final ArtifactDeploymentManager artifactDeploymentManager; private final ArtifactDownloadManager artifactDownloadManager; - public ModelController(IDistributionClient client, ModelLoaderConfig config, EventCallback eventCallback, ArtifactDeploymentManager artifactDeploymentManager, ArtifactDownloadManager artifactDownloadManager) { + public ModelController(IDistributionClient client, ModelLoaderConfig config, ArtifactDeploymentManager artifactDeploymentManager, ArtifactDownloadManager artifactDownloadManager) { this.client = client; this.config = config; - this.eventCallback = eventCallback; this.artifactDeploymentManager = artifactDeploymentManager; this.artifactDownloadManager = artifactDownloadManager; } - @PostConstruct - protected void start() { - if (!config.getASDCConnectionDisabled()) { - initSdcClient(); - } - } - - /** - * Responsible for stopping the connection to the distribution client before the resource is destroyed. - */ - public void preShutdownOperations() { - logger.info(ModelLoaderMsgs.STOPPING_CLIENT); - if (client != null) { - client.stop(); - } - } - - /** - * Responsible for loading configuration files, initializing model distribution clients, and starting them. - */ - protected void initSdcClient() { - // Initialize distribution client - logger.debug(ModelLoaderMsgs.INITIALIZING, "Initializing distribution client..."); - IDistributionClientResult initResult = client.init(config, eventCallback); - - if (initResult.getDistributionActionResult() == DistributionActionResultEnum.SUCCESS) { - // Start distribution client - logger.debug(ModelLoaderMsgs.INITIALIZING, "Starting distribution client..."); - IDistributionClientResult startResult = client.start(); - if (startResult.getDistributionActionResult() == DistributionActionResultEnum.SUCCESS) { - logger.info(ModelLoaderMsgs.INITIALIZING, "Connection to SDC established"); - } else { - String errorMsg = "Failed to start distribution client: " + startResult.getDistributionMessageResult(); - logger.error(ModelLoaderMsgs.ASDC_CONNECTION_ERROR, errorMsg); - - // Kick off a timer to retry the SDC connection - Timer timer = new Timer(); - TimerTask task = new SdcConnectionJob(client, config, eventCallback, timer); - timer.schedule(task, new Date(), 60000); - } - } else { - String errorMsg = "Failed to initialize distribution client: " + initResult.getDistributionMessageResult(); - logger.error(ModelLoaderMsgs.ASDC_CONNECTION_ERROR, errorMsg); - - // Kick off a timer to retry the SDC connection - Timer timer = new Timer(); - TimerTask task = new SdcConnectionJob(client, config, eventCallback, timer); - timer.schedule(task, new Date(), 60000); - } - - Runtime.getRuntime().addShutdownHook(new Thread(this::preShutdownOperations)); - } - /** * (non-Javadoc) * diff --git a/src/test/java/org/onap/aai/modelloader/service/TestModelController.java b/src/test/java/org/onap/aai/modelloader/service/TestModelController.java index 82f7203..9d7d5ea 100644 --- a/src/test/java/org/onap/aai/modelloader/service/TestModelController.java +++ b/src/test/java/org/onap/aai/modelloader/service/TestModelController.java @@ -31,7 +31,6 @@ import java.util.Collections; import javax.ws.rs.core.Response; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; @@ -39,7 +38,6 @@ import org.onap.aai.modelloader.config.ModelLoaderConfig; import org.onap.aai.modelloader.extraction.VnfCatalogExtractor; import org.onap.aai.modelloader.notification.ArtifactDownloadManager; import org.onap.aai.modelloader.notification.BabelArtifactConverter; -import org.onap.aai.modelloader.notification.EventCallback; import org.onap.aai.modelloader.notification.NotificationPublisher; import org.onap.aai.modelloader.restclient.BabelServiceClient; import org.onap.aai.modelloader.restclient.BabelServiceClientException; @@ -59,7 +57,6 @@ public class TestModelController { @Autowired IDistributionClient iDistributionClient; @Autowired ModelLoaderConfig modelLoaderConfig; - @Autowired EventCallback eventCallback; @Autowired ArtifactDeploymentManager artifactDeploymentManager; @Autowired BabelArtifactConverter babelArtifactConverter; @Autowired NotificationPublisher notificationPublisher; @@ -75,12 +72,7 @@ public class TestModelController { when(clientFactory.create(any())).thenReturn(babelServiceClient); when(babelServiceClient.postArtifact(any(), any(), any(), any())).thenReturn(Collections.emptyList()); ArtifactDownloadManager artifactDownloadManager = new ArtifactDownloadManager(iDistributionClient, modelLoaderConfig, clientFactory, babelArtifactConverter, notificationPublisher, vnfCatalogExtractor); - this.modelController = new ModelController(iDistributionClient, modelLoaderConfig, eventCallback, artifactDeploymentManager, artifactDownloadManager); - } - - @AfterEach - public void shutdown() { - modelController.preShutdownOperations(); + this.modelController = new ModelController(iDistributionClient, modelLoaderConfig, artifactDeploymentManager, artifactDownloadManager); } @Test diff --git a/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderServiceWithSdc.java b/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderServiceWithSdc.java index a8501be..6dc1b3d 100644 --- a/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderServiceWithSdc.java +++ b/src/test/java/org/onap/aai/modelloader/service/TestModelLoaderServiceWithSdc.java @@ -28,7 +28,6 @@ import java.util.Base64; import javax.ws.rs.core.Response; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.onap.aai.modelloader.util.ArtifactTestUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -44,17 +43,12 @@ import org.springframework.test.context.TestPropertySource; public class TestModelLoaderServiceWithSdc { @Autowired - private ModelController service; - - @AfterEach - public void shutdown() { - service.preShutdownOperations(); - } + private ModelController controller; @Test public void testIngestModel() throws IOException { byte[] csarPayload = new ArtifactTestUtils().loadResource("compressedArtifacts/service-VscpaasTest-csar.csar"); - Response response = service.ingestModel("model-name", "", Base64.getEncoder().encodeToString(csarPayload)); + Response response = controller.ingestModel("model-name", "", Base64.getEncoder().encodeToString(csarPayload)); assertThat(response.getStatus(), is(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode())); } -- 2.16.6 From df2ad94ee9b641a4c2c19969816a6275f6d056e3 Mon Sep 17 00:00:00 2001 From: Fiete Ostkamp Date: Thu, 11 Apr 2024 20:25:29 +0200 Subject: [PATCH 13/16] Add model-loader integration tests - add integration tests that assert the external communication towards other services using Wiremock - remove tests that are asserting getters and setters of objects Issue-ID: AAI-3826 Change-Id: I1f627801869f40cb0eaa61b10148b41bd3b1bdb8 Signed-off-by: Fiete Ostkamp --- pom.xml | 11 + .../onap/aai/modelloader/config/BeanConfig.java | 18 +- .../config/DistributionClientStartupConfig.java | 7 +- .../aai/modelloader/config/ModelLoaderConfig.java | 34 ++++ .../notification/NotificationDataImpl.java | 94 ++------- .../aai/modelloader/restclient/AaiRestClient.java | 2 + .../restclient/HttpsBabelServiceClient.java | 4 +- .../aai/modelloader/service/ArtifactInfoImpl.java | 109 +--------- .../DistributionClientTestConfiguration.java | 81 ++++++++ .../modelloader/TestModelLoaderApplication.java | 44 ---- .../distribution/NotificationIntegrationTest.java | 14 +- .../notification/ArtifactDownloadManagerTest.java | 107 ++++++++++ .../notification/ModelArtifactHandlerTest.java | 226 +++++++++++++++++++++ .../notification/TestNotificationDataImpl.java | 83 -------- .../restclient/TestBabelServiceClient.java | 90 ++++---- .../aai/modelloader/restclient/TracingTest.java | 63 ++++++ .../modelloader/service/TestArtifactInfoImpl.java | 123 ----------- src/test/resources/__files/artifactTypes.json | 63 ++++++ src/test/resources/__files/kafkaBootstrap.json | 5 + src/test/resources/__files/modelResponse.xml | 7 + .../service-TestSvc-csar-babel-response.json | 1 + .../resources/__files/service-TestSvc-csar.csar | Bin 0 -> 55342 bytes src/test/resources/application.properties | 9 +- src/test/resources/logback-test.xml | 2 +- src/test/resources/model-loader.properties | 8 +- 25 files changed, 696 insertions(+), 509 deletions(-) create mode 100644 src/test/java/org/onap/aai/modelloader/DistributionClientTestConfiguration.java delete mode 100644 src/test/java/org/onap/aai/modelloader/TestModelLoaderApplication.java create mode 100644 src/test/java/org/onap/aai/modelloader/notification/ArtifactDownloadManagerTest.java create mode 100644 src/test/java/org/onap/aai/modelloader/notification/ModelArtifactHandlerTest.java delete mode 100644 src/test/java/org/onap/aai/modelloader/notification/TestNotificationDataImpl.java create mode 100644 src/test/java/org/onap/aai/modelloader/restclient/TracingTest.java delete mode 100644 src/test/java/org/onap/aai/modelloader/service/TestArtifactInfoImpl.java create mode 100644 src/test/resources/__files/artifactTypes.json create mode 100644 src/test/resources/__files/kafkaBootstrap.json create mode 100644 src/test/resources/__files/modelResponse.xml create mode 100644 src/test/resources/__files/service-TestSvc-csar-babel-response.json create mode 100644 src/test/resources/__files/service-TestSvc-csar.csar diff --git a/pom.xml b/pom.xml index 644e408..1bf31f4 100644 --- a/pom.xml +++ b/pom.xml @@ -384,6 +384,17 @@ org.springframework.boot spring-boot-starter-test test + + + com.vaadin.external.google + android-json + + + + + org.springframework.cloud + spring-cloud-contract-wiremock + test diff --git a/src/main/java/org/onap/aai/modelloader/config/BeanConfig.java b/src/main/java/org/onap/aai/modelloader/config/BeanConfig.java index 8f7b2bb..63956e2 100644 --- a/src/main/java/org/onap/aai/modelloader/config/BeanConfig.java +++ b/src/main/java/org/onap/aai/modelloader/config/BeanConfig.java @@ -30,9 +30,11 @@ import org.onap.aai.cl.eelf.LoggerFactory; import org.onap.aai.modelloader.service.ModelLoaderMsgs; import org.onap.sdc.api.IDistributionClient; import org.onap.sdc.impl.DistributionClientFactory; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; @Configuration public class BeanConfig { @@ -44,13 +46,18 @@ public class BeanConfig { private String configDir; @Bean - public ModelLoaderConfig modelLoaderConfig() throws IOException { + public Properties configProperties() throws IOException { // Load model loader system configuration logger.info(ModelLoaderMsgs.LOADING_CONFIGURATION); - ModelLoaderConfig.setConfigHome(configDir); - Properties configProperties = new Properties(); InputStream configInputStream = Files.newInputStream(Paths.get(configDir, "model-loader.properties")); + Properties configProperties = new Properties(); configProperties.load(configInputStream); + return configProperties; + } + + @Bean + public ModelLoaderConfig modelLoaderConfig(Properties configProperties) { + ModelLoaderConfig.setConfigHome(configDir); return new ModelLoaderConfig(configProperties); } @@ -58,4 +65,9 @@ public class BeanConfig { public IDistributionClient iDistributionClient() { return DistributionClientFactory.createDistributionClient(); } + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } } diff --git a/src/main/java/org/onap/aai/modelloader/config/DistributionClientStartupConfig.java b/src/main/java/org/onap/aai/modelloader/config/DistributionClientStartupConfig.java index 84c79f2..cd9d919 100644 --- a/src/main/java/org/onap/aai/modelloader/config/DistributionClientStartupConfig.java +++ b/src/main/java/org/onap/aai/modelloader/config/DistributionClientStartupConfig.java @@ -33,10 +33,10 @@ import org.onap.sdc.api.results.IDistributionClientResult; import org.onap.sdc.utils.DistributionActionResultEnum; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.annotation.Configuration; import org.springframework.context.event.EventListener; -import org.springframework.stereotype.Component; -@Component +@Configuration @ConditionalOnProperty(value = "ml.distribution.connection.enabled", havingValue = "true", matchIfMissing = true) public class DistributionClientStartupConfig { @@ -57,7 +57,8 @@ public class DistributionClientStartupConfig { protected void initSdcClient() { // Initialize distribution client logger.debug(ModelLoaderMsgs.INITIALIZING, "Initializing distribution client..."); - IDistributionClientResult initResult = client.init(config, eventCallback); + IDistributionClientResult initResult = null; + initResult = client.init(config, eventCallback); if (initResult.getDistributionActionResult() == DistributionActionResultEnum.SUCCESS) { // Start distribution client diff --git a/src/main/java/org/onap/aai/modelloader/config/ModelLoaderConfig.java b/src/main/java/org/onap/aai/modelloader/config/ModelLoaderConfig.java index 7da90d9..6723e75 100644 --- a/src/main/java/org/onap/aai/modelloader/config/ModelLoaderConfig.java +++ b/src/main/java/org/onap/aai/modelloader/config/ModelLoaderConfig.java @@ -77,6 +77,9 @@ public class ModelLoaderConfig implements IConfiguration { protected static final String PROP_ML_DISTRIBUTION_HTTP_PROXY_PORT = PREFIX_DISTRIBUTION_CLIENT + "HTTP_PROXY_PORT"; protected static final String PROP_ML_DISTRIBUTION_HTTPS_PROXY_HOST = PREFIX_DISTRIBUTION_CLIENT + "HTTPS_PROXY_HOST"; protected static final String PROP_ML_DISTRIBUTION_HTTPS_PROXY_PORT = PREFIX_DISTRIBUTION_CLIENT + "HTTPS_PROXY_PORT"; + protected static final String PROP_ML_DISTRIBUTION_SASL_JAAS_CONFIG = PREFIX_DISTRIBUTION_CLIENT + "SASL_JAAS_CONFIG"; + protected static final String PROP_ML_DISTRIBUTION_SASL_MECHANISM = PREFIX_DISTRIBUTION_CLIENT + "SASL_MECHANISM"; + protected static final String PROP_ML_DISTRIBUTION_SECURITY_PROTOCOL = PREFIX_DISTRIBUTION_CLIENT + "SECURITY_PROTOCOL"; protected static final String PROP_AAI_BASE_URL = PREFIX_AAI + "BASE_URL"; protected static final String PROP_AAI_KEYSTORE_FILE = PREFIX_AAI + SUFFIX_KEYSTORE_FILE; protected static final String PROP_AAI_KEYSTORE_PASSWORD = PREFIX_AAI + SUFFIX_KEYSTORE_PASS; @@ -413,4 +416,35 @@ public class ModelLoaderConfig implements IConfiguration { return Integer.parseInt(connectTimeout); } + @Override + public String getKafkaSaslJaasConfig() { + String saslJaasConfFromEnv = System.getenv("SASL_JAAS_CONFIG"); + if(saslJaasConfFromEnv != null) { + return saslJaasConfFromEnv; + } + if(get(PROP_ML_DISTRIBUTION_SASL_JAAS_CONFIG) != null) { + return get(PROP_ML_DISTRIBUTION_SASL_JAAS_CONFIG); + } + return null; + } + + @Override + public String getKafkaSaslMechanism() { + if(get(PROP_ML_DISTRIBUTION_SASL_MECHANISM) != null) { + return get(PROP_ML_DISTRIBUTION_SASL_MECHANISM); + } + return System.getenv().getOrDefault("SASL_MECHANISM", "SCRAM-SHA-512"); + } + + /** + * One of PLAINTEXT, SSL, SASL_PLAINTEXT, SASL_SSL + */ + @Override + public String getKafkaSecurityProtocolConfig() { + if(get(PROP_ML_DISTRIBUTION_SECURITY_PROTOCOL) != null) { + return get(PROP_ML_DISTRIBUTION_SECURITY_PROTOCOL); + } + return System.getenv().getOrDefault("SECURITY_PROTOCOL", "SASL_PLAINTEXT"); + } + } diff --git a/src/main/java/org/onap/aai/modelloader/notification/NotificationDataImpl.java b/src/main/java/org/onap/aai/modelloader/notification/NotificationDataImpl.java index 566415e..3eb07f3 100644 --- a/src/main/java/org/onap/aai/modelloader/notification/NotificationDataImpl.java +++ b/src/main/java/org/onap/aai/modelloader/notification/NotificationDataImpl.java @@ -20,98 +20,28 @@ */ package org.onap.aai.modelloader.notification; -import java.util.Collections; import java.util.List; import org.onap.sdc.api.notification.IArtifactInfo; import org.onap.sdc.api.notification.INotificationData; import org.onap.sdc.api.notification.IResourceInstance; +import lombok.Data; + +@Data public class NotificationDataImpl implements INotificationData { private String distributionID; + private String serviceName; + private String serviceVersion; + private String serviceUUID; + private String serviceDescription; + private List resources; + private List serviceArtifacts; + private String serviceInvariantUUID; + private String workloadContext; @Override - public IArtifactInfo getArtifactMetadataByUUID(String arg0) { - return null; - } - - @Override - public String getDistributionID() { - return distributionID; - } - - public void setDistributionID(String distributionID) { - this.distributionID = distributionID; - } - - @Override - public List getResources() { - return Collections.emptyList(); - } - - @Override - public List getServiceArtifacts() { - return Collections.emptyList(); - } - - @Override - public String getServiceDescription() { - return null; - } - - @Override - public String getServiceInvariantUUID() { - return null; - } - - @Override - public String getServiceName() { - return null; - } - - @Override - public String getServiceUUID() { - return null; - } - - @Override - public String getServiceVersion() { + public IArtifactInfo getArtifactMetadataByUUID(String uuid) { return null; } - - @Override - public String getWorkloadContext() { - return null; - } - - @Override - public void setWorkloadContext(String arg0) { - // Unsupported method - not expected to be called - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((distributionID == null) ? 0 : distributionID.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - NotificationDataImpl other = (NotificationDataImpl) obj; - if (distributionID == null) { - if (other.distributionID != null) - return false; - } else if (!distributionID.equals(other.distributionID)) - return false; - return true; - } - } diff --git a/src/main/java/org/onap/aai/modelloader/restclient/AaiRestClient.java b/src/main/java/org/onap/aai/modelloader/restclient/AaiRestClient.java index 40aeacc..45f84d6 100644 --- a/src/main/java/org/onap/aai/modelloader/restclient/AaiRestClient.java +++ b/src/main/java/org/onap/aai/modelloader/restclient/AaiRestClient.java @@ -42,6 +42,7 @@ import org.onap.aai.modelloader.service.ModelLoaderMsgs; import org.onap.aai.restclient.client.OperationResult; import org.onap.aai.restclient.client.RestClient; import org.onap.aai.restclient.enums.RestAuthenticationMode; +import org.springframework.stereotype.Component; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -52,6 +53,7 @@ import org.xml.sax.SAXException; * Wrapper around the standard A&AI Rest Client interface. This currently uses Jersey client 1.x * */ +@Component public class AaiRestClient { public static final String HEADER_TRANS_ID = "X-TransactionId"; diff --git a/src/main/java/org/onap/aai/modelloader/restclient/HttpsBabelServiceClient.java b/src/main/java/org/onap/aai/modelloader/restclient/HttpsBabelServiceClient.java index c76996f..0789996 100644 --- a/src/main/java/org/onap/aai/modelloader/restclient/HttpsBabelServiceClient.java +++ b/src/main/java/org/onap/aai/modelloader/restclient/HttpsBabelServiceClient.java @@ -229,8 +229,8 @@ public class HttpsBabelServiceClient implements BabelServiceClient { String resourceUrl = config.getBabelBaseUrl() + config.getBabelGenerateArtifactsUrl(); WebResource webResource = client.resource(resourceUrl); ClientResponse response = webResource.type("application/json") - .header(AaiRestClient.HEADER_TRANS_ID, Collections.singletonList(transactionId)) - .header(AaiRestClient.HEADER_FROM_APP_ID, Collections.singletonList(AaiRestClient.ML_APP_NAME)) + .header(AaiRestClient.HEADER_TRANS_ID, transactionId) + .header(AaiRestClient.HEADER_FROM_APP_ID, AaiRestClient.ML_APP_NAME) .post(ClientResponse.class, obj.toString()); String sanitizedJson = JsonSanitizer.sanitize(response.getEntity(String.class)); diff --git a/src/main/java/org/onap/aai/modelloader/service/ArtifactInfoImpl.java b/src/main/java/org/onap/aai/modelloader/service/ArtifactInfoImpl.java index 9af92be..f83e44d 100644 --- a/src/main/java/org/onap/aai/modelloader/service/ArtifactInfoImpl.java +++ b/src/main/java/org/onap/aai/modelloader/service/ArtifactInfoImpl.java @@ -20,116 +20,25 @@ */ package org.onap.aai.modelloader.service; -import java.util.Collections; import java.util.List; import org.onap.sdc.api.notification.IArtifactInfo; +import lombok.Data; + /** * This class is an implementation of IArtifactInfo for test purposes. */ +@Data public class ArtifactInfoImpl implements IArtifactInfo { private String artifactName; private String artifactType; private String artifactDescription; private String artifactVersion; - - @Override - public String getArtifactName() { - return artifactName; - } - - public void setArtifactName(String artifactName) { - this.artifactName = artifactName; - } - - @Override - public String getArtifactType() { - return artifactType; - } - - public void setArtifactType(String artifactType) { - this.artifactType = artifactType; - } - - @Override - public String getArtifactURL() { - return null; - } - - @Override - public String getArtifactChecksum() { - return null; - } - - @Override - public String getArtifactDescription() { - return artifactDescription; - } - - public void setArtifactDescription(String artifactDescription) { - this.artifactDescription = artifactDescription; - } - - @Override - public Integer getArtifactTimeout() { - return null; - } - - @Override - public String getArtifactVersion() { - return artifactVersion; - } - - public void setArtifactVersion(String artifactVersion) { - this.artifactVersion = artifactVersion; - } - - @Override - public String getArtifactUUID() { - return null; - } - - @Override - public IArtifactInfo getGeneratedArtifact() { - return null; - } - - @Override - public List getRelatedArtifacts() { - return Collections.emptyList(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - ArtifactInfoImpl that = (ArtifactInfoImpl) o; - - if (artifactName != null ? !artifactName.equals(that.artifactName) : that.artifactName != null) { - return false; - } - if (artifactType != null ? !artifactType.equals(that.artifactType) : that.artifactType != null) { - return false; - } - if (artifactDescription != null ? !artifactDescription.equals(that.artifactDescription) - : that.artifactDescription != null) { - return false; - } - return artifactVersion != null ? artifactVersion.equals(that.artifactVersion) : that.artifactVersion == null; - } - - @Override - public int hashCode() { - int result = artifactName != null ? artifactName.hashCode() : 0; - result = 31 * result + (artifactType != null ? artifactType.hashCode() : 0); - result = 31 * result + (artifactDescription != null ? artifactDescription.hashCode() : 0); - result = 31 * result + (artifactVersion != null ? artifactVersion.hashCode() : 0); - return result; - } + private String artifactURL; + private String artifactChecksum; + private Integer artifactTimeout; + private String artifactUUID; + private IArtifactInfo generatedArtifact; + private List relatedArtifacts; } diff --git a/src/test/java/org/onap/aai/modelloader/DistributionClientTestConfiguration.java b/src/test/java/org/onap/aai/modelloader/DistributionClientTestConfiguration.java new file mode 100644 index 0000000..1eed62f --- /dev/null +++ b/src/test/java/org/onap/aai/modelloader/DistributionClientTestConfiguration.java @@ -0,0 +1,81 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2024 Deutsche Telekom AG Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +package org.onap.aai.modelloader; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.matching; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Properties; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.context.event.EventListener; + +@TestConfiguration +public class DistributionClientTestConfiguration { + + @Value("${CONFIG_HOME}") + private String configDir; + + @Value("${wiremock.server.port}") + private int wiremockPort; + + @Primary + @Bean(name = "testProperties") + public Properties configProperties() throws IOException { + // Load model loader system configuration + InputStream configInputStream = Files.newInputStream(Paths.get(configDir, "model-loader.properties")); + Properties configProperties = new Properties(); + configProperties.load(configInputStream); + + setOverrides(configProperties); + + return configProperties; + } + + private void setOverrides(Properties configProperties) { + configProperties.setProperty("ml.distribution.ASDC_ADDRESS", "localhost:" + wiremockPort); + configProperties.setProperty("ml.babel.BASE_URL", "http://localhost:" + wiremockPort); + } + + @EventListener(ApplicationStartedEvent.class) + public void mockSdcInit() { + stubFor(get(urlEqualTo("/sdc/v1/artifactTypes")) + .withHeader("X-ECOMP-RequestID", matching(".+")) + .withHeader("X-ECOMP-InstanceID", equalTo("aai-ml-id-test")) + .willReturn(aResponse().withHeader("Content-Type", "application/json").withBodyFile("artifactTypes.json"))); + + stubFor(get(urlEqualTo("/sdc/v1/distributionKafkaData")) + .withHeader("X-ECOMP-RequestID", matching(".+")) + .withHeader("X-ECOMP-InstanceID", equalTo("aai-ml-id-test")) + .willReturn(aResponse().withHeader("Content-Type", "application/json").withBodyFile("kafkaBootstrap.json"))); + } +} \ No newline at end of file diff --git a/src/test/java/org/onap/aai/modelloader/TestModelLoaderApplication.java b/src/test/java/org/onap/aai/modelloader/TestModelLoaderApplication.java deleted file mode 100644 index cd39066..0000000 --- a/src/test/java/org/onap/aai/modelloader/TestModelLoaderApplication.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017-2018 European Software Marketing Ltd. - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============LICENSE_END========================================================= - */ -package org.onap.aai.modelloader; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -/** - * Tests for ModelLoaderApplication class. - * - */ -public class TestModelLoaderApplication { - - static { - System.setProperty("CONFIG_HOME", "src/test/resources"); - } - - @Test - public void testServiceStarts() { - // The SDC Distribution Client is disabled. - ModelLoaderApplication.main(new String[0]); - assertTrue(true); - } - -} diff --git a/src/test/java/org/onap/aai/modelloader/distribution/NotificationIntegrationTest.java b/src/test/java/org/onap/aai/modelloader/distribution/NotificationIntegrationTest.java index 2645871..abdefd4 100644 --- a/src/test/java/org/onap/aai/modelloader/distribution/NotificationIntegrationTest.java +++ b/src/test/java/org/onap/aai/modelloader/distribution/NotificationIntegrationTest.java @@ -26,6 +26,7 @@ import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.onap.aai.modelloader.notification.NotificationDataImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; @@ -35,25 +36,26 @@ import org.springframework.test.annotation.DirtiesContext; @DirtiesContext @SpringBootTest(properties = { "model-loader.sdc.connection.enabled=true"}) -@EmbeddedKafka(partitions = 1, brokerProperties = { "listeners=PLAINTEXT://localhost:9092", "port=9092" }) +@EmbeddedKafka(partitions = 1, ports = 9092, topics = {"${topics.distribution.notification}"}) public class NotificationIntegrationTest { @Autowired EventCallbackAspect eventCallbackAspect; @Autowired - private KafkaTemplate kafkaTemplate; + private KafkaTemplate kafkaTemplate; - @Value("${test.topic}") + @Value("${topics.distribution.notification}") private String topic; @Test @Disabled("This test is not yet implemented") public void thatActivateCallbackIsCalled() throws Exception { - String data = "Smth"; - + NotificationDataImpl notificationData = new NotificationDataImpl(); + notificationData.setDistributionID("distributionID"); + // TODO: send distribution event here - kafkaTemplate.send(topic, data); + kafkaTemplate.send(topic, notificationData); // TODO: mock distribution client requests to /sdc/v1/artifactTypes // TODO: mock distribution client requests to /sdc/v1/distributionKafkaData diff --git a/src/test/java/org/onap/aai/modelloader/notification/ArtifactDownloadManagerTest.java b/src/test/java/org/onap/aai/modelloader/notification/ArtifactDownloadManagerTest.java new file mode 100644 index 0000000..0985790 --- /dev/null +++ b/src/test/java/org/onap/aai/modelloader/notification/ArtifactDownloadManagerTest.java @@ -0,0 +1,107 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2024 Deutsche Telekom AG Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +package org.onap.aai.modelloader.notification; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.matching; +import static com.github.tomakehurst.wiremock.client.WireMock.matchingJsonPath; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.http.entity.ContentType; +import org.junit.jupiter.api.Test; +import org.onap.aai.modelloader.DistributionClientTestConfiguration; +import org.onap.aai.modelloader.entity.Artifact; +import org.onap.aai.modelloader.entity.ArtifactType; +import org.onap.aai.modelloader.service.ArtifactInfoImpl; +import org.onap.sdc.api.notification.IArtifactInfo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.kafka.test.context.EmbeddedKafka; +import org.springframework.test.annotation.DirtiesContext; + +import com.fasterxml.jackson.core.JsonProcessingException; + +@DirtiesContext +@AutoConfigureWireMock(port = 0) +@EmbeddedKafka(partitions = 1, ports = 9092, topics = {"${topics.distribution.notification}"}) +@SpringBootTest(properties = { "ml.distribution.connection.enabled=true" }) +@Import(DistributionClientTestConfiguration.class) +public class ArtifactDownloadManagerTest { + + @Autowired ArtifactDownloadManager artifactDownloadManager; + + @Test + public void downloadArtifacts() throws JsonProcessingException { + NotificationDataImpl notificationData = new NotificationDataImpl(); + notificationData.setDistributionID("distributionID"); + notificationData.setServiceVersion("2.0"); + + stubFor(get(urlEqualTo("/sdc/v1/catalog/services/DemovlbCds/1.0/artifacts/service-TestSvc-csar.csar")) + .withHeader("Accept", equalTo(ContentType.APPLICATION_OCTET_STREAM.toString())) + .withHeader("X-ECOMP-RequestID", matching(".+")) + .withHeader("X-ECOMP-InstanceID", equalTo("aai-ml-id-test")) + .willReturn(aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_OCTET_STREAM.toString()) + .withBodyFile("service-TestSvc-csar.csar"))); + + stubFor( + post(urlEqualTo("/services/babel-service/v1/app/generateArtifacts")) + .withHeader("X-TransactionId", equalTo("distributionID")) + .withHeader("X-FromAppId", equalTo("ModelLoader")) + .withHeader("Content-Type", equalTo(MediaType.APPLICATION_JSON_VALUE)) + .withRequestBody(matchingJsonPath("$.artifactName", equalTo("service-TestSvc-csar.csar"))) + .withRequestBody(matchingJsonPath("$.artifactVersion", equalTo("2.0"))) + .withRequestBody(matchingJsonPath("$.csar", matching(".*"))) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json") + .withBodyFile("service-TestSvc-csar-babel-response.json"))); + + ArtifactInfoImpl artifactInfo = new ArtifactInfoImpl(); + artifactInfo.setArtifactName("service-TestSvc-csar.csar"); + artifactInfo.setArtifactVersion("1.0"); + artifactInfo.setArtifactURL("/sdc/v1/catalog/services/DemovlbCds/1.0/artifacts/service-TestSvc-csar.csar"); + artifactInfo.setArtifactType("TOSCA_CSAR"); + artifactInfo.setArtifactChecksum("ZmI5NzQ1MWViZGFkMjRjZWEwNTQzY2U0OWQwYjlmYjQ="); + artifactInfo.setArtifactUUID("f6f907f1-3f45-4fb4-8cbe-15a4c6ee16db"); + List artifacts = new ArrayList<>(); + artifacts.add(artifactInfo); + List modelArtifacts = new ArrayList<>(); // processed artifacts will be written to this list + List catalogArtifacts = new ArrayList<>(); // processed artifacts will be written to this list + boolean result = artifactDownloadManager.downloadArtifacts(notificationData, artifacts, modelArtifacts, catalogArtifacts); + + assertEquals(1, modelArtifacts.size()); + assertEquals(ArtifactType.MODEL, modelArtifacts.get(0).getType()); + assertTrue(result); + } + +} diff --git a/src/test/java/org/onap/aai/modelloader/notification/ModelArtifactHandlerTest.java b/src/test/java/org/onap/aai/modelloader/notification/ModelArtifactHandlerTest.java new file mode 100644 index 0000000..b1269ee --- /dev/null +++ b/src/test/java/org/onap/aai/modelloader/notification/ModelArtifactHandlerTest.java @@ -0,0 +1,226 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2024 Deutsche Telekom AG Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +package org.onap.aai.modelloader.notification; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.deleteRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.onap.aai.modelloader.config.ModelLoaderConfig; +import org.onap.aai.modelloader.entity.Artifact; +import org.onap.aai.modelloader.entity.model.ModelArtifact; +import org.onap.aai.modelloader.entity.model.ModelArtifactHandler; +import org.onap.aai.modelloader.restclient.AaiRestClient; +import org.onap.aai.restclient.client.OperationResult; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock; +import org.springframework.http.HttpStatus; +import org.springframework.web.client.RestTemplate; +import org.w3c.dom.Node; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.client.WireMock; + +import java.util.ArrayList; + +@SpringBootTest +@AutoConfigureWireMock(port = 0) +public class ModelArtifactHandlerTest { + + @Value("${wiremock.server.port}") + private int wiremockPort; + + final ObjectMapper objectMapper = new ObjectMapper(); + @Mock + ModelLoaderConfig config; + @InjectMocks + ModelArtifactHandler modelArtifactHandler; + + @Autowired + AaiRestClient restClient; + + @BeforeEach + public void setUp() { + when(config.getAaiBaseUrl()).thenReturn("http://localhost:" + wiremockPort); + when(config.getAaiModelUrl(any())).thenReturn("/aai/v28/service-design-and-creation/models/model/"); + } + + @Test + public void thatArtifactsCanBeCreated() { + WireMock.stubFor( + WireMock.get(urlEqualTo("/aai/v28/service-design-and-creation/models/model/modelInvariantId")) + .withHeader("Accept", equalTo("application/xml")) + .withHeader("X-TransactionId", equalTo("someId")) + .withHeader("X-FromAppId", equalTo("ModelLoader")) + .willReturn( + WireMock.aResponse() + .withStatus(HttpStatus.NOT_FOUND.value()))); + + WireMock.stubFor( + WireMock.put(urlEqualTo("/aai/v28/service-design-and-creation/models/model/modelInvariantId")) + .withHeader("Content-Type", equalTo("application/xml")) + .withHeader("X-TransactionId", equalTo("someId")) + .withHeader("X-FromAppId", equalTo("ModelLoader")) + .willReturn( + WireMock.aResponse() + .withStatus(HttpStatus.CREATED.value()))); + + ModelArtifact modelArtifact = new ModelArtifact(); + modelArtifact.setModelInvariantId("modelInvariantId"); + List artifacts = List.of(modelArtifact); + List completedArtifacts = new ArrayList<>(); + + boolean result = modelArtifactHandler.pushArtifacts(artifacts, + "someId", completedArtifacts, restClient); + assertTrue(result); + WireMock.verify( + WireMock.putRequestedFor(urlEqualTo("/aai/v28/service-design-and-creation/models/model/modelInvariantId"))); + } + + @Test + public void thatArtifactsCanBeUpdated() { + // Checks if model exists in resources service + WireMock.stubFor( + WireMock.get(urlEqualTo("/aai/v28/service-design-and-creation/models/model/modelInvariantId")) + .withHeader("Accept", equalTo("application/xml")) + .withHeader("X-TransactionId", equalTo("distributionId")) + .withHeader("X-FromAppId", equalTo("ModelLoader")) + .willReturn( + WireMock.aResponse() + .withStatus(HttpStatus.OK.value()))); + + // Checks if specific version of model exists in aai-resources + WireMock.stubFor( + WireMock.get(urlEqualTo( + "/aai/v28/service-design-and-creation/models/model/modelInvariantId/model-vers/model-ver/modelVersionId")) + .withHeader("Accept", equalTo("application/xml")) + .withHeader("X-TransactionId", equalTo("distributionId")) + .withHeader("X-FromAppId", equalTo("ModelLoader")) + .willReturn( + WireMock.aResponse() + .withStatus(HttpStatus.NOT_FOUND.value()))); + + WireMock.stubFor( + WireMock.put(urlEqualTo( + "/aai/v28/service-design-and-creation/models/model/modelInvariantId/model-vers/model-ver/modelVersionId")) + .withHeader("Content-Type", equalTo("application/xml")) + .withHeader("X-TransactionId", equalTo("distributionId")) + .withHeader("X-FromAppId", equalTo("ModelLoader")) + .willReturn( + WireMock.aResponse() + .withStatus(HttpStatus.CREATED.value()))); + + ModelArtifact modelArtifact = new ModelArtifact(); + modelArtifact.setModelInvariantId("modelInvariantId"); + modelArtifact.setModelVerId("modelVersionId"); + Node node = Mockito.mock(Node.class); + modelArtifact.setModelVer(node); + List artifacts = List.of(modelArtifact); + List completedArtifacts = new ArrayList<>(); + + boolean result = modelArtifactHandler.pushArtifacts(artifacts, + "distributionId", completedArtifacts, restClient); + verify(WireMock.putRequestedFor(urlEqualTo( + "/aai/v28/service-design-and-creation/models/model/modelInvariantId/model-vers/model-ver/modelVersionId"))); + assertTrue(result); + } + + @Test + public void thatModelCanBeRolledBack() { + stubFor(WireMock.get(urlEqualTo("/aai/v28/service-design-and-creation/models/model/3a40ab73-6694-4e75-bb6d-9a4a86ce35b3")) + .withHeader("X-FromAppId", equalTo("ModelLoader")) + .withHeader("X-TransactionId", equalTo("distributionId")) + .willReturn(aResponse() + .withBodyFile("modelResponse.xml"))); + + stubFor(WireMock.delete(urlEqualTo("/aai/v28/service-design-and-creation/models/model/3a40ab73-6694-4e75-bb6d-9a4a86ce35b3?resource-version=1710523260974")) + .withHeader("X-FromAppId", equalTo("ModelLoader")) + .withHeader("X-TransactionId", equalTo("distributionId")) + .willReturn(aResponse().withStatus(HttpStatus.OK.value()))); + + ModelArtifact modelArtifact = new ModelArtifact(); + modelArtifact.setModelInvariantId("3a40ab73-6694-4e75-bb6d-9a4a86ce35b3"); + modelArtifact.setModelVerId("modelVersionId"); + Node node = Mockito.mock(Node.class); + modelArtifact.setModelVer(node); + List completedArtifacts = new ArrayList<>(); + completedArtifacts.add(modelArtifact); + + mockModelCreation(modelArtifact); + + modelArtifactHandler.rollback(completedArtifacts, "distributionId", restClient); + + verify( + deleteRequestedFor( + urlEqualTo("/aai/v28/service-design-and-creation/models/model/3a40ab73-6694-4e75-bb6d-9a4a86ce35b3?resource-version=1710523260974"))); + } + + /** + * To test the rollback of the full model, the ModelArtifact must have the + * firstVersionOfModel = true state. + * This flag is set during the model creation and thus needs to run before + * testing this particular aspect. + * + * @param modelArtifact + */ + private void mockModelCreation(ModelArtifact modelArtifact) { + OperationResult createdResult = Mockito.mock(OperationResult.class); + when(createdResult.getResultCode()).thenReturn(HttpStatus.CREATED.value()); + OperationResult notFoundResult = Mockito.mock(OperationResult.class); + when(notFoundResult.getResultCode()).thenReturn(HttpStatus.NOT_FOUND.value()); + AaiRestClient aaiClient = Mockito.mock(AaiRestClient.class); + when(aaiClient.putResource(any(), any(), any(), any())).thenReturn(createdResult); + when(aaiClient.getResource(any(), any(), any())).thenReturn(notFoundResult); + modelArtifact.push(aaiClient, config, null, new ArrayList<>()); + } + + @Test + public void thatModelVersionCanBeRolledBack() { + + // "http://localhost:10594/aai/v28/service-design-and-creation/models/model/modelInvariantId/model-vers/model-ver/modelVersionId" + + ModelArtifact modelArtifact = new ModelArtifact(); + modelArtifact.setModelInvariantId("modelInvariantId"); + modelArtifact.setModelVerId("modelVersionId"); + Node node = Mockito.mock(Node.class); + modelArtifact.setModelVer(node); + List completedArtifacts = new ArrayList<>(); + completedArtifacts.add(modelArtifact); + + modelArtifactHandler.rollback(completedArtifacts, "distributionId", restClient); + + } +} \ No newline at end of file diff --git a/src/test/java/org/onap/aai/modelloader/notification/TestNotificationDataImpl.java b/src/test/java/org/onap/aai/modelloader/notification/TestNotificationDataImpl.java deleted file mode 100644 index 4d6858a..0000000 --- a/src/test/java/org/onap/aai/modelloader/notification/TestNotificationDataImpl.java +++ /dev/null @@ -1,83 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017-2018 European Software Marketing Ltd. - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============LICENSE_END========================================================= - */ -package org.onap.aai.modelloader.notification; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.MatcherAssert.assertThat; - -import org.junit.jupiter.api.Test; - -/** - * Tests for NotificationDataImpl class - * - */ -public class TestNotificationDataImpl { - - @Test - public void testGettersAndSetters() { - NotificationDataImpl data = new NotificationDataImpl(); - String distributionId = "testid"; - - data.setDistributionID(distributionId); - assertThat(data.getDistributionID(), is(equalTo(distributionId))); - - // Getters return empty data - assertThat(data.getArtifactMetadataByUUID(null), is(equalTo(null))); - assertThat(data.getServiceDescription(), is(equalTo(null))); - assertThat(data.getServiceInvariantUUID(), is(equalTo(null))); - assertThat(data.getServiceName(), is(equalTo(null))); - assertThat(data.getServiceUUID(), is(equalTo(null))); - assertThat(data.getServiceVersion(), is(equalTo(null))); - assertThat(data.getResources().size(), is(0)); - assertThat(data.getServiceArtifacts().size(), is(0)); - - // Unsupported method! - String context = "testcontext"; - data.setWorkloadContext(context); - assertThat(data.getWorkloadContext(), is(equalTo(null))); - } - - - @Test - public void testEquality() { - NotificationDataImpl data = new NotificationDataImpl(); - assertThat(data, is(not(equalTo(null)))); - assertThat(data, is(not(equalTo("")))); // NOSONAR - assertThat(data, is(equalTo(data))); - - NotificationDataImpl other = new NotificationDataImpl(); - assertThat(data, is(equalTo(other))); - assertThat(data.hashCode(), is(equalTo(other.hashCode()))); - - other.setDistributionID(""); - assertThat(data, is(not(equalTo(other)))); - - data.setDistributionID("1234"); - assertThat(data, is(not(equalTo(other)))); - - other.setDistributionID("1234"); - assertThat(data, is(equalTo(other))); - assertThat(data.hashCode(), is(equalTo(other.hashCode()))); - } - -} diff --git a/src/test/java/org/onap/aai/modelloader/restclient/TestBabelServiceClient.java b/src/test/java/org/onap/aai/modelloader/restclient/TestBabelServiceClient.java index 77e1594..169943c 100644 --- a/src/test/java/org/onap/aai/modelloader/restclient/TestBabelServiceClient.java +++ b/src/test/java/org/onap/aai/modelloader/restclient/TestBabelServiceClient.java @@ -20,68 +20,65 @@ */ package org.onap.aai.modelloader.restclient; -import static javax.servlet.http.HttpServletResponse.SC_OK; -import static org.apache.commons.io.IOUtils.write; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import java.io.IOException; import java.net.URISyntaxException; -import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.ArrayList; import java.util.List; import java.util.Properties; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.onap.aai.babel.service.data.BabelArtifact; import org.onap.aai.modelloader.config.ModelLoaderConfig; import org.onap.aai.modelloader.service.HttpsBabelServiceClientFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock; -import com.google.gson.Gson; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.matching.EqualToPattern; /** * Local testing of the Babel service client. * */ +@SpringBootTest +@AutoConfigureWireMock(port = 0) public class TestBabelServiceClient { - private Server server; - private String responseBody; - - @BeforeEach - public void startJetty() throws Exception { - List response = new ArrayList<>(); - response.add(new BabelArtifact("", null, "")); - response.add(new BabelArtifact("", null, "")); - response.add(new BabelArtifact("", null, "")); - responseBody = new Gson().toJson(response); - - server = new Server(0); - server.setHandler(getMockHandler()); - server.start(); - } + @Value("${wiremock.server.port}") + private int wiremockPort; - @AfterEach - public void stopJetty() throws Exception { - server.stop(); + @BeforeAll + public static void setup() throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + List artifacts = List.of( + new BabelArtifact("art1", null, ""), + new BabelArtifact("art2", null, ""), + new BabelArtifact("art3", null, "")); + WireMock.stubFor( + WireMock.post(WireMock.urlEqualTo("/generate")) + .withHeader("X-TransactionId", WireMock.equalTo("Test-Transaction-ID-BabelClient")) + .withHeader("X-FromAppId", WireMock.equalTo("ModelLoader")) + .withRequestBody(WireMock.matchingJsonPath("$.artifactName", WireMock.equalTo("service-Vscpass-Test"))) + .withRequestBody(WireMock.matchingJsonPath("$.artifactVersion", WireMock.equalTo("1.0"))) + .withRequestBody(WireMock.matchingJsonPath("$.csar", WireMock.matching(".*"))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", "application/json") + .withBody(objectMapper.writeValueAsString(artifacts)))); } @Test public void testRestClient() throws BabelServiceClientException, IOException, URISyntaxException { - String url = server.getURI().toString(); + String url = "http://localhost:" + wiremockPort; Properties configProperties = new Properties(); configProperties.put("ml.babel.KEYSTORE_PASSWORD", "OBF:1vn21ugu1saj1v9i1v941sar1ugw1vo0"); configProperties.put("ml.babel.KEYSTORE_FILE", "src/test/resources/auth/aai-client-dummy.p12"); @@ -89,7 +86,7 @@ public class TestBabelServiceClient { // In a real deployment this would be a different file (to the client keystore) configProperties.put("ml.babel.TRUSTSTORE_FILE", "src/test/resources/auth/aai-client-dummy.p12"); configProperties.put("ml.babel.BASE_URL", url); - configProperties.put("ml.babel.GENERATE_ARTIFACTS_URL", "generate"); + configProperties.put("ml.babel.GENERATE_ARTIFACTS_URL", "/generate"); configProperties.put("ml.aai.RESTCLIENT_CONNECT_TIMEOUT", "12000"); configProperties.put("ml.aai.RESTCLIENT_READ_TIMEOUT", "12000"); BabelServiceClient client = @@ -102,11 +99,11 @@ public class TestBabelServiceClient { @Test public void testRestClientHttp() throws BabelServiceClientException, IOException, URISyntaxException { - String url = server.getURI().toString(); + String url = "http://localhost:" + wiremockPort; Properties configProperties = new Properties(); configProperties.put("ml.babel.USE_HTTPS", "false"); configProperties.put("ml.babel.BASE_URL", url); - configProperties.put("ml.babel.GENERATE_ARTIFACTS_URL", "generate"); + configProperties.put("ml.babel.GENERATE_ARTIFACTS_URL", "/generate"); configProperties.put("ml.aai.RESTCLIENT_CONNECT_TIMEOUT", "3000"); configProperties.put("ml.aai.RESTCLIENT_READ_TIMEOUT", "3000"); BabelServiceClient client = @@ -121,23 +118,4 @@ public class TestBabelServiceClient { private byte[] readBytesFromFile(String resourceFile) throws IOException, URISyntaxException { return Files.readAllBytes(Paths.get(ClassLoader.getSystemResource(resourceFile).toURI())); } - - /** - * Creates an {@link AbstractHandler handler} returning an arbitrary String as a response. - * - * @return never null. - */ - private Handler getMockHandler() { - Handler handler = new AbstractHandler() { - @Override - public void handle(String target, Request request, HttpServletRequest servletRequest, - HttpServletResponse response) throws IOException, ServletException { - response.setStatus(SC_OK); - response.setContentType("text/xml;charset=utf-8"); - write(responseBody, response.getOutputStream(), Charset.defaultCharset()); - request.setHandled(true); - } - }; - return handler; - } } diff --git a/src/test/java/org/onap/aai/modelloader/restclient/TracingTest.java b/src/test/java/org/onap/aai/modelloader/restclient/TracingTest.java new file mode 100644 index 0000000..3bfceb5 --- /dev/null +++ b/src/test/java/org/onap/aai/modelloader/restclient/TracingTest.java @@ -0,0 +1,63 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2024 Deutsche Telekom AG Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +package org.onap.aai.modelloader.restclient; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock; +import org.springframework.http.HttpStatus; +import org.springframework.web.client.RestTemplate; + +import com.github.tomakehurst.wiremock.client.WireMock; + +@SpringBootTest(properties = { + "spring.sleuth.enabled=true", + "spring.zipkin.baseUrl=http://localhost:${wiremock.server.port}" +}) +@AutoConfigureWireMock(port = 0) +public class TracingTest { + + @Value("${wiremock.server.port}") + private int wiremockPort; + + @Autowired RestTemplate restTemplate; + + @Test + public void thatArtifactsCanBePushed() { + WireMock.stubFor( + WireMock.post(WireMock.urlEqualTo("/api/v2/spans")) + .willReturn( + WireMock.aResponse() + .withStatus(HttpStatus.OK.value()))); + + WireMock.stubFor( + WireMock.get(WireMock.urlEqualTo("/")) + .withHeader("X-B3-TraceId", WireMock.matching(".*")) + .willReturn( + WireMock.aResponse() + .withStatus(HttpStatus.OK.value()))); + + + String response = restTemplate.getForObject("http://localhost:" + wiremockPort + "/", String.class); + } + +} diff --git a/src/test/java/org/onap/aai/modelloader/service/TestArtifactInfoImpl.java b/src/test/java/org/onap/aai/modelloader/service/TestArtifactInfoImpl.java deleted file mode 100644 index d1d46f0..0000000 --- a/src/test/java/org/onap/aai/modelloader/service/TestArtifactInfoImpl.java +++ /dev/null @@ -1,123 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017-2018 European Software Marketing Ltd. - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============LICENSE_END========================================================= - */ -package org.onap.aai.modelloader.service; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.empty; - -import org.junit.jupiter.api.Test; - -/** - * Tests for NotificationDataImpl class - * - */ -public class TestArtifactInfoImpl { - - @Test - public void testGettersAndSetters() { - ArtifactInfoImpl info = new ArtifactInfoImpl(); - String artifactName = "testname"; - - info.setArtifactName(artifactName); - assertThat(info.getArtifactName(), is(equalTo(artifactName))); - - String artifactType = "test-type"; - info.setArtifactType(artifactType); - assertThat(info.getArtifactType(), is(equalTo(artifactType))); - - String artifactVersion = "v1"; - info.setArtifactVersion(artifactVersion); - assertThat(info.getArtifactVersion(), is(equalTo(artifactVersion))); - - String artifactDescription = "test description"; - info.setArtifactDescription(artifactDescription); - assertThat(info.getArtifactDescription(), is(equalTo(artifactDescription))); - - assertThat(info.getArtifactChecksum(), is(nullValue())); - assertThat(info.getArtifactTimeout(), is(nullValue())); - assertThat(info.getArtifactURL(), is(nullValue())); - assertThat(info.getArtifactUUID(), is(nullValue())); - assertThat(info.getGeneratedArtifact(), is(nullValue())); - assertThat(info.getRelatedArtifacts(), is(empty())); - } - - - @Test - public void testEquality() { - ArtifactInfoImpl info = new ArtifactInfoImpl(); - assertThat(info, is(not(equalTo(null)))); - assertThat(info, is(not(equalTo("")))); // NOSONAR - assertThat(info, is(equalTo(info))); - - ArtifactInfoImpl other = new ArtifactInfoImpl(); - assertThat(info, is(equalTo(other))); - assertThat(info.hashCode(), is(equalTo(other.hashCode()))); - - // Artifact Name - other.setArtifactName(""); - assertThat(info, is(not(equalTo(other)))); - - info.setArtifactName("1234"); - assertThat(info, is(not(equalTo(other)))); - - other.setArtifactName("1234"); - assertThat(info, is(equalTo(other))); - assertThat(info.hashCode(), is(equalTo(other.hashCode()))); - - // Artifact Type - other.setArtifactType(""); - assertThat(info, is(not(equalTo(other)))); - - info.setArtifactType("type"); - assertThat(info, is(not(equalTo(other)))); - - other.setArtifactType("type"); - assertThat(info, is(equalTo(other))); - assertThat(info.hashCode(), is(equalTo(other.hashCode()))); - - // Artifact Description - other.setArtifactDescription(""); - assertThat(info, is(not(equalTo(other)))); - - info.setArtifactDescription("type"); - assertThat(info, is(not(equalTo(other)))); - - other.setArtifactDescription("type"); - assertThat(info, is(equalTo(other))); - assertThat(info.hashCode(), is(equalTo(other.hashCode()))); - - // Artifact Version - other.setArtifactVersion(""); - assertThat(info, is(not(equalTo(other)))); - - info.setArtifactVersion("v1"); - assertThat(info, is(not(equalTo(other)))); - - other.setArtifactVersion("v1"); - assertThat(info, is(equalTo(other))); - assertThat(info.hashCode(), is(equalTo(other.hashCode()))); - } - -} diff --git a/src/test/resources/__files/artifactTypes.json b/src/test/resources/__files/artifactTypes.json new file mode 100644 index 0000000..de3cfce --- /dev/null +++ b/src/test/resources/__files/artifactTypes.json @@ -0,0 +1,63 @@ +[ + "AAI_SERVICE_MODEL", + "AAI_VF_INSTANCE_MODEL", + "AAI_VF_MODEL", + "AAI_VF_MODULE_MODEL", + "ANSIBLE_PLAYBOOK", + "APPC_CONFIG", + "BPEL", + "CHEF", + "CLOUD_TECHNOLOGY_SPECIFIC_ARTIFACT", + "CONTROLLER_BLUEPRINT_ARCHIVE", + "DCAE_DOC", + "DCAE_EVENT", + "DCAE_INVENTORY_BLUEPRINT", + "DCAE_INVENTORY_DOC", + "DCAE_INVENTORY_EVENT", + "DCAE_INVENTORY_JSON", + "DCAE_INVENTORY_POLICY", + "DCAE_INVENTORY_TOSCA", + "DCAE_JSON", + "DCAE_POLICY", + "DCAE_TOSCA", + "DG_XML", + "ETSI_PACKAGE", + "ASD_PACKAGE", + "GUIDE", + "HEAT_ARTIFACT", + "HEAT_ENV", + "HEAT", + "HEAT_NESTED", + "HEAT_NET", + "HEAT_VOL", + "HELM", + "ICON", + "LIFECYCLE_OPERATIONS", + "MODEL_INVENTORY_PROFILE", + "MODEL_QUERY_SPEC", + "MURANO_PKG", + "NETWORK_CALL_FLOW", + "ONBOARDED_PACKAGE", + "OTHER", + "PERFORMANCE_COUNTER", + "PLAN", + "PM_DICTIONARY", + "PNF_SW_INFORMATION", + "PUPPET", + "SHELL_SCRIPT", + "SHELL", + "SNMP_POLL", + "SNMP_TRAP", + "TOSCA_CSAR", + "TOSCA_TEMPLATE", + "UCPE_LAYER_2_CONFIGURATION", + "VENDOR_LICENSE", + "VES_EVENTS", + "VF_LICENSE", + "VF_MODULES_METADATA", + "VNF_CATALOG", + "WORKFLOW", + "YANG_MODULE", + "YANG_XML", + "YANG" +] \ No newline at end of file diff --git a/src/test/resources/__files/kafkaBootstrap.json b/src/test/resources/__files/kafkaBootstrap.json new file mode 100644 index 0000000..1820ade --- /dev/null +++ b/src/test/resources/__files/kafkaBootstrap.json @@ -0,0 +1,5 @@ +{ + "kafkaBootStrapServer": "localhost:9092", + "distrNotificationTopicName": "SDC-DISTR-NOTIF-TOPIC-AUTO", + "distrStatusTopicName": "SDC-DISTR-STATUS-TOPIC-AUTO" +} \ No newline at end of file diff --git a/src/test/resources/__files/modelResponse.xml b/src/test/resources/__files/modelResponse.xml new file mode 100644 index 0000000..36d07d8 --- /dev/null +++ b/src/test/resources/__files/modelResponse.xml @@ -0,0 +1,7 @@ + + + 3a40ab73-6694-4e75-bb6d-9a4a86ce35b3 + service + Network Service + 1710523260974 + \ No newline at end of file diff --git a/src/test/resources/__files/service-TestSvc-csar-babel-response.json b/src/test/resources/__files/service-TestSvc-csar-babel-response.json new file mode 100644 index 0000000..7103156 --- /dev/null +++ b/src/test/resources/__files/service-TestSvc-csar-babel-response.json @@ -0,0 +1 @@ +[{"name":"AAI-test-svc-service-2.0.xml","type":"MODEL","payload":"\n 3c8bc8e7-e387-46ed-8616-70e99e2206dc\n service\n Network L1-3\n \n \n 71f47717-b100-4eac-940b-7d4e86a4cbb7\n test-svc\n 2.0\n test-svc\n \n \n T\n unbounded\n \n \n \n model-ver\n \n model-ver.model-version-id\n 46b92144-923a-4d20-b85a-3cbd847668a9\n \n \n model.model-invariant-id\n 82194af1-3c2c-485a-8f44-420e22a9eaa4\n \n \n \n \n \n \n \n"}] \ No newline at end of file diff --git a/src/test/resources/__files/service-TestSvc-csar.csar b/src/test/resources/__files/service-TestSvc-csar.csar new file mode 100644 index 0000000000000000000000000000000000000000..711f06813bbdec898be82708ed78e643ad8df7ee GIT binary patch literal 55342 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 Y(x$3_i - + diff --git a/src/test/resources/model-loader.properties b/src/test/resources/model-loader.properties index 4c24679..2db7c09 100644 --- a/src/test/resources/model-loader.properties +++ b/src/test/resources/model-loader.properties @@ -1,16 +1,20 @@ # Model Loader Distribution Client Configuration ml.distribution.ACTIVE_SERVER_TLS_AUTH=false ml.distribution.ASDC_ADDRESS=localhost:8443 +ml.distribution.ASDC_USE_HTTPS=false ml.distribution.CONSUMER_GROUP=aai-ml-group-test ml.distribution.CONSUMER_ID=aai-ml-id-test -ml.distribution.ENVIRONMENT_NAME=env +ml.distribution.ENVIRONMENT_NAME=AUTO ml.distribution.KEYSTORE_PASSWORD= ml.distribution.KEYSTORE_FILE= ml.distribution.PASSWORD=Aa123456 -ml.distribution.POLLING_INTERVAL=5 +ml.distribution.POLLING_INTERVAL=15 ml.distribution.POLLING_TIMEOUT=3 ml.distribution.USER=ci ml.distribution.ARTIFACT_TYPES=MODEL_QUERY_SPEC,TOSCA_CSAR +ml.distribution.SASL_JAAS_CONFIG=org.apache.kafka.common.security.scram.ScramLoginModule required username="aai-modelloader-ku" password="somePassword"; +ml.distribution.SECURITY_PROTOCOL=PLAINTEXT +ml.distribution.SASL_MECHANISM=PLAIN # Disable ASDC polling & enable REST interface ml.distribution.ASDC_CONNECTION_DISABLE=true -- 2.16.6 From f265fce93af9ccb200162fbeaa0705b418397851 Mon Sep 17 00:00:00 2001 From: Fiete Ostkamp Date: Thu, 11 Apr 2024 10:23:00 +0200 Subject: [PATCH 14/16] Release model-loader 1.13.6 - release should fix model distribution Issue-ID: AAI-3825 Change-Id: I2a2daedeaa5c604a8175e6a673a723a25dd8ed1a Signed-off-by: Fiete Ostkamp --- releases/1.13.6-container-release.yaml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 releases/1.13.6-container-release.yaml diff --git a/releases/1.13.6-container-release.yaml b/releases/1.13.6-container-release.yaml new file mode 100644 index 0000000..873cd80 --- /dev/null +++ b/releases/1.13.6-container-release.yaml @@ -0,0 +1,7 @@ +distribution_type: container +container_release_tag: 1.13.6 +project: model-loader +ref: df2ad94ee9b641a4c2c19969816a6275f6d056e3 +containers: + - name: model-loader + version: 1.13-STAGING-20240417T124725Z -- 2.16.6 From 0e9a90774a86475f40d4e946798cf765910a31f6 Mon Sep 17 00:00:00 2001 From: Fiete Ostkamp Date: Wed, 17 Apr 2024 16:06:40 +0200 Subject: [PATCH 15/16] Refactor Babel related code in model-loader - introduce BabelArtifactService - simplify babel http client by removing factory and https support Issue-ID: AAI-3827 Change-Id: I5c004d342687c8aaa8f26927342752d472f05da3 Signed-off-by: Fiete Ostkamp --- pom.xml | 2 +- .../onap/aai/modelloader/babel/BabelArtifact.java | 35 +++ .../modelloader/babel/BabelArtifactService.java | 100 ++++++++ .../onap/aai/modelloader/config/BeanConfig.java | 1 - .../notification/ArtifactDownloadManager.java | 97 +------- .../notification/BabelArtifactConverter.java | 4 +- .../modelloader/restclient/BabelServiceClient.java | 4 +- .../restclient/BabelServiceClientImpl.java | 99 ++++++++ .../restclient/HttpsBabelServiceClient.java | 257 --------------------- .../service/HttpsBabelServiceClientFactory.java | 56 ----- .../modelloader/BabelClientTestConfiguration.java | 57 +++++ .../ArtifactDownloadManagerVnfcTest.java | 14 +- .../notification/ModelArtifactHandlerTest.java | 2 - .../notification/TestArtifactDownloadManager.java | 22 +- .../restclient/MockBabelServiceClient.java | 5 +- .../restclient/TestBabelServiceClient.java | 52 ++--- .../modelloader/service/TestModelController.java | 7 +- 17 files changed, 350 insertions(+), 464 deletions(-) create mode 100644 src/main/java/org/onap/aai/modelloader/babel/BabelArtifact.java create mode 100644 src/main/java/org/onap/aai/modelloader/babel/BabelArtifactService.java create mode 100644 src/main/java/org/onap/aai/modelloader/restclient/BabelServiceClientImpl.java delete mode 100644 src/main/java/org/onap/aai/modelloader/restclient/HttpsBabelServiceClient.java delete mode 100644 src/main/java/org/onap/aai/modelloader/service/HttpsBabelServiceClientFactory.java create mode 100644 src/test/java/org/onap/aai/modelloader/BabelClientTestConfiguration.java diff --git a/pom.xml b/pom.xml index 1bf31f4..2efcd39 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ org.onap.aai.model-loader model-loader aai-model-loader - 1.13.6-SNAPSHOT + 1.14.0-SNAPSHOT diff --git a/src/main/java/org/onap/aai/modelloader/babel/BabelArtifact.java b/src/main/java/org/onap/aai/modelloader/babel/BabelArtifact.java new file mode 100644 index 0000000..6de318b --- /dev/null +++ b/src/main/java/org/onap/aai/modelloader/babel/BabelArtifact.java @@ -0,0 +1,35 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2024 Deutsche Telekom AG Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +package org.onap.aai.modelloader.babel; + +import org.onap.aai.modelloader.entity.ArtifactType; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class BabelArtifact { + String name; + public ArtifactType type; + String payload; +} diff --git a/src/main/java/org/onap/aai/modelloader/babel/BabelArtifactService.java b/src/main/java/org/onap/aai/modelloader/babel/BabelArtifactService.java new file mode 100644 index 0000000..1221861 --- /dev/null +++ b/src/main/java/org/onap/aai/modelloader/babel/BabelArtifactService.java @@ -0,0 +1,100 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2024 Deutsche Telekom AG Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +package org.onap.aai.modelloader.babel; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.onap.aai.babel.service.data.BabelArtifact; +import org.onap.aai.babel.service.data.BabelRequest; +import org.onap.aai.babel.service.data.BabelArtifact.ArtifactType; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.modelloader.entity.Artifact; +import org.onap.aai.modelloader.entity.model.BabelArtifactParsingException; +import org.onap.aai.modelloader.notification.BabelArtifactConverter; +import org.onap.aai.modelloader.notification.ProcessToscaArtifactsException; +import org.onap.aai.modelloader.restclient.BabelServiceClient; +import org.onap.aai.modelloader.service.ModelLoaderMsgs; +import org.springframework.stereotype.Service; + +@Service +public class BabelArtifactService { + + private static Logger logger = LoggerFactory.getInstance().getLogger(BabelArtifactService.class); + + private final BabelServiceClient babelServiceClient; + private final BabelArtifactConverter babelArtifactConverter; + + public BabelArtifactService(BabelServiceClient babelServiceClient, BabelArtifactConverter babelArtifactConverter) { + this.babelServiceClient = babelServiceClient; + this.babelArtifactConverter = babelArtifactConverter; + } + + public void invokeBabelService(List modelArtifacts, List catalogArtifacts, BabelRequest babelRequest, String distributionId) + throws ProcessToscaArtifactsException { + try { + logger.info(ModelLoaderMsgs.DISTRIBUTION_EVENT, + "Posting artifact: " + babelRequest.getArtifactName() + ", service version: " + babelRequest.getArtifactVersion() + + ", artifact version: " + babelRequest.getArtifactVersion()); + + List babelArtifacts = + babelServiceClient.postArtifact(babelRequest, distributionId); + + // Sort Babel artifacts based on type + Map> artifactMap = + babelArtifacts.stream().collect(Collectors.groupingBy(BabelArtifact::getType)); + + if (artifactMap.containsKey(BabelArtifact.ArtifactType.MODEL)) { + modelArtifacts.addAll( + babelArtifactConverter.convertToModel(artifactMap.get(BabelArtifact.ArtifactType.MODEL))); + artifactMap.remove(BabelArtifact.ArtifactType.MODEL); + } + + if (artifactMap.containsKey(BabelArtifact.ArtifactType.VNFCATALOG)) { + catalogArtifacts.addAll(babelArtifactConverter + .convertToCatalog(artifactMap.get(BabelArtifact.ArtifactType.VNFCATALOG))); + artifactMap.remove(BabelArtifact.ArtifactType.VNFCATALOG); + } + + // Log unexpected artifact types + if (!artifactMap.isEmpty()) { + logger.warn(ModelLoaderMsgs.ARTIFACT_PARSE_ERROR, + babelRequest.getArtifactName() + " " + babelRequest.getArtifactVersion() + + ". Unexpected artifact types returned by the babel service: " + + artifactMap.keySet().toString()); + } + + } catch (BabelArtifactParsingException e) { + logger.error(ModelLoaderMsgs.ARTIFACT_PARSE_ERROR, + "Error for artifact " + babelRequest.getArtifactName() + " " + babelRequest.getArtifactVersion() + " " + e); + throw new ProcessToscaArtifactsException( + "An error occurred while trying to parse the Babel artifacts: " + e.getLocalizedMessage()); + } catch (Exception e) { + logger.error(ModelLoaderMsgs.BABEL_REST_REQUEST_ERROR, e, "POST", + "Error posting artifact " + babelRequest.getArtifactName() + " " + babelRequest.getArtifactVersion() + " to Babel: " + + e.getLocalizedMessage()); + throw new ProcessToscaArtifactsException( + "An error occurred while calling the Babel service: " + e.getLocalizedMessage()); + } + } + +} diff --git a/src/main/java/org/onap/aai/modelloader/config/BeanConfig.java b/src/main/java/org/onap/aai/modelloader/config/BeanConfig.java index 63956e2..cc6702b 100644 --- a/src/main/java/org/onap/aai/modelloader/config/BeanConfig.java +++ b/src/main/java/org/onap/aai/modelloader/config/BeanConfig.java @@ -30,7 +30,6 @@ import org.onap.aai.cl.eelf.LoggerFactory; import org.onap.aai.modelloader.service.ModelLoaderMsgs; import org.onap.sdc.api.IDistributionClient; import org.onap.sdc.impl.DistributionClientFactory; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/org/onap/aai/modelloader/notification/ArtifactDownloadManager.java b/src/main/java/org/onap/aai/modelloader/notification/ArtifactDownloadManager.java index dcec799..f0c96bd 100644 --- a/src/main/java/org/onap/aai/modelloader/notification/ArtifactDownloadManager.java +++ b/src/main/java/org/onap/aai/modelloader/notification/ArtifactDownloadManager.java @@ -24,24 +24,18 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.Base64; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import org.onap.aai.babel.service.data.BabelArtifact; -import org.onap.aai.babel.service.data.BabelArtifact.ArtifactType; +import org.onap.aai.babel.service.data.BabelRequest; import org.onap.aai.cl.api.Logger; import org.onap.aai.cl.eelf.LoggerFactory; import org.onap.aai.cl.mdc.MdcContext; import org.onap.aai.cl.mdc.MdcOverride; -import org.onap.aai.modelloader.config.ModelLoaderConfig; +import org.onap.aai.modelloader.babel.BabelArtifactService; import org.onap.aai.modelloader.entity.Artifact; import org.onap.aai.modelloader.entity.model.BabelArtifactParsingException; import org.onap.aai.modelloader.entity.model.IModelParser; import org.onap.aai.modelloader.entity.model.NamedQueryArtifactParser; import org.onap.aai.modelloader.extraction.InvalidArchiveException; import org.onap.aai.modelloader.extraction.VnfCatalogExtractor; -import org.onap.aai.modelloader.restclient.BabelServiceClient; -import org.onap.aai.modelloader.restclient.BabelServiceClientException; -import org.onap.aai.modelloader.service.BabelServiceClientFactory; import org.onap.aai.modelloader.service.ModelLoaderMsgs; import org.onap.sdc.api.IDistributionClient; import org.onap.sdc.api.notification.IArtifactInfo; @@ -68,19 +62,15 @@ public class ArtifactDownloadManager { private final IDistributionClient client; private final NotificationPublisher notificationPublisher; - private final BabelArtifactConverter babelArtifactConverter; - private final ModelLoaderConfig config; - private final BabelServiceClientFactory clientFactory; private final VnfCatalogExtractor vnfCatalogExtractor; + private final BabelArtifactService babelArtifactService; - public ArtifactDownloadManager(IDistributionClient client, ModelLoaderConfig config, - BabelServiceClientFactory clientFactory, BabelArtifactConverter babelArtifactConverter, NotificationPublisher notificationPublisher, VnfCatalogExtractor vnfCatalogExtractor) { + public ArtifactDownloadManager(IDistributionClient client, + NotificationPublisher notificationPublisher, VnfCatalogExtractor vnfCatalogExtractor, BabelArtifactService babelArtifactService) { this.client = client; this.notificationPublisher = notificationPublisher; - this.babelArtifactConverter = babelArtifactConverter; - this.config = config; - this.clientFactory = clientFactory; this.vnfCatalogExtractor = vnfCatalogExtractor; + this.babelArtifactService = babelArtifactService; } /** @@ -158,7 +148,11 @@ public class ArtifactDownloadManager { IArtifactInfo artifactInfo, String distributionId, String serviceVersion) throws ProcessToscaArtifactsException, InvalidArchiveException { // Get translated artifacts from Babel Service - invokeBabelService(modelArtifacts, catalogArtifacts, payload, artifactInfo, distributionId, serviceVersion); + BabelRequest babelRequest = new BabelRequest(); + babelRequest.setArtifactName(artifactInfo.getArtifactName()); + babelRequest.setCsar(Base64.getEncoder().encodeToString(payload)); + babelRequest.setArtifactVersion(serviceVersion); + babelArtifactService.invokeBabelService(modelArtifacts, catalogArtifacts, babelRequest, distributionId); // Get VNF Catalog artifacts directly from CSAR List csarCatalogArtifacts = vnfCatalogExtractor.extract(payload, artifactInfo.getArtifactName()); @@ -173,75 +167,6 @@ public class ArtifactDownloadManager { } } - public void invokeBabelService(List modelArtifacts, List catalogArtifacts, byte[] payload, - IArtifactInfo artifactInfo, String distributionId, String serviceVersion) - throws ProcessToscaArtifactsException { - try { - BabelServiceClient babelClient = createBabelServiceClient(artifactInfo, serviceVersion); - - logger.info(ModelLoaderMsgs.DISTRIBUTION_EVENT, - "Posting artifact: " + artifactInfo.getArtifactName() + ", service version: " + serviceVersion - + ", artifact version: " + artifactInfo.getArtifactVersion()); - - List babelArtifacts = - babelClient.postArtifact(payload, artifactInfo.getArtifactName(), serviceVersion, distributionId); - - // Sort Babel artifacts based on type - Map> artifactMap = - babelArtifacts.stream().collect(Collectors.groupingBy(BabelArtifact::getType)); - - if (artifactMap.containsKey(BabelArtifact.ArtifactType.MODEL)) { - modelArtifacts.addAll( - babelArtifactConverter.convertToModel(artifactMap.get(BabelArtifact.ArtifactType.MODEL))); - artifactMap.remove(BabelArtifact.ArtifactType.MODEL); - } - - if (artifactMap.containsKey(BabelArtifact.ArtifactType.VNFCATALOG)) { - catalogArtifacts.addAll(babelArtifactConverter - .convertToCatalog(artifactMap.get(BabelArtifact.ArtifactType.VNFCATALOG))); - artifactMap.remove(BabelArtifact.ArtifactType.VNFCATALOG); - } - - // Log unexpected artifact types - if (!artifactMap.isEmpty()) { - logger.warn(ModelLoaderMsgs.ARTIFACT_PARSE_ERROR, - artifactInfo.getArtifactName() + " " + serviceVersion - + ". Unexpected artifact types returned by the babel service: " - + artifactMap.keySet().toString()); - } - - } catch (BabelArtifactParsingException e) { - logger.error(ModelLoaderMsgs.ARTIFACT_PARSE_ERROR, - "Error for artifact " + artifactInfo.getArtifactName() + " " + serviceVersion + e); - throw new ProcessToscaArtifactsException( - "An error occurred while trying to parse the Babel artifacts: " + e.getLocalizedMessage()); - } catch (Exception e) { - logger.error(ModelLoaderMsgs.BABEL_REST_REQUEST_ERROR, e, "POST", config.getBabelBaseUrl(), - "Error posting artifact " + artifactInfo.getArtifactName() + " " + serviceVersion + " to Babel: " - + e.getLocalizedMessage()); - throw new ProcessToscaArtifactsException( - "An error occurred while calling the Babel service: " + e.getLocalizedMessage()); - } - } - - BabelServiceClient createBabelServiceClient(IArtifactInfo artifact, String serviceVersion) - throws ProcessToscaArtifactsException { - BabelServiceClient babelClient; - try { - logger.debug(ModelLoaderMsgs.DISTRIBUTION_EVENT, "Creating Babel client"); - babelClient = clientFactory.create(config); - } catch (BabelServiceClientException e) { - logger.error(ModelLoaderMsgs.BABEL_REST_REQUEST_ERROR, e, "POST", config.getBabelBaseUrl(), - "Error posting artifact " + artifact.getArtifactName() + " " + serviceVersion + " to Babel: " - + e.getLocalizedMessage()); - throw new ProcessToscaArtifactsException( - "An error occurred tyring to convert the tosca artifacts to xml artifacts: " - + e.getLocalizedMessage()); - } - - return babelClient; - } - private void processModelQuerySpecArtifact(List modelArtifacts, IDistributionClientDownloadResult downloadResult) throws BabelArtifactParsingException { logger.debug(ModelLoaderMsgs.DISTRIBUTION_EVENT, "Processing named query artifact."); diff --git a/src/main/java/org/onap/aai/modelloader/notification/BabelArtifactConverter.java b/src/main/java/org/onap/aai/modelloader/notification/BabelArtifactConverter.java index 480a461..5118652 100644 --- a/src/main/java/org/onap/aai/modelloader/notification/BabelArtifactConverter.java +++ b/src/main/java/org/onap/aai/modelloader/notification/BabelArtifactConverter.java @@ -46,7 +46,7 @@ public class BabelArtifactConverter { * @throws BabelArtifactParsingException if an error occurs trying to parse the generated XML files that were * converted from tosca artifacts */ - List convertToModel(List xmlArtifacts) throws BabelArtifactParsingException { + public List convertToModel(List xmlArtifacts) throws BabelArtifactParsingException { Objects.requireNonNull(xmlArtifacts); List modelArtifacts = new ArrayList<>(); ModelArtifactParser modelArtParser = new ModelArtifactParser(); @@ -72,7 +72,7 @@ public class BabelArtifactConverter { * @param xmlArtifacts xml artifacts to be parsed * @return List list of converted catalog artifacts */ - List convertToCatalog(List xmlArtifacts) { + public List convertToCatalog(List xmlArtifacts) { Objects.requireNonNull(xmlArtifacts); List catalogArtifacts = new ArrayList<>(); diff --git a/src/main/java/org/onap/aai/modelloader/restclient/BabelServiceClient.java b/src/main/java/org/onap/aai/modelloader/restclient/BabelServiceClient.java index f69752b..8328c42 100644 --- a/src/main/java/org/onap/aai/modelloader/restclient/BabelServiceClient.java +++ b/src/main/java/org/onap/aai/modelloader/restclient/BabelServiceClient.java @@ -23,10 +23,10 @@ package org.onap.aai.modelloader.restclient; import java.util.List; import org.onap.aai.babel.service.data.BabelArtifact; +import org.onap.aai.babel.service.data.BabelRequest; public interface BabelServiceClient { - List postArtifact(byte[] artifactPayload, String artifactName, String artifactVersion, - String transactionId) throws BabelServiceClientException; + List postArtifact(BabelRequest babelRequest, String transactionId) throws BabelServiceClientException; } diff --git a/src/main/java/org/onap/aai/modelloader/restclient/BabelServiceClientImpl.java b/src/main/java/org/onap/aai/modelloader/restclient/BabelServiceClientImpl.java new file mode 100644 index 0000000..5400892 --- /dev/null +++ b/src/main/java/org/onap/aai/modelloader/restclient/BabelServiceClientImpl.java @@ -0,0 +1,99 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017-2019 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017-2019 European Software Marketing Ltd. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.aai.modelloader.restclient; + +import java.util.List; +import java.util.stream.Collectors; +import org.onap.aai.babel.service.data.BabelArtifact; +import org.onap.aai.babel.service.data.BabelRequest; +import org.onap.aai.babel.service.data.BabelArtifact.ArtifactType; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.modelloader.config.ModelLoaderConfig; +import org.onap.aai.modelloader.service.ModelLoaderMsgs; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +/** + * HTTPS Client for interfacing with Babel. + * + */ +@Component +public class BabelServiceClientImpl implements BabelServiceClient { + + private static final Logger logger = LoggerFactory.getInstance().getLogger(BabelServiceClientImpl.class); + private final ModelLoaderConfig config; + private final RestTemplate restTemplate; + + public BabelServiceClientImpl(ModelLoaderConfig config, RestTemplate restTemplate) { + this.config = config; + this.restTemplate = restTemplate; + } + + @Override + public List postArtifact(BabelRequest babelRequest, String transactionId) throws BabelServiceClientException { + if (logger.isInfoEnabled()) { + logger.info(ModelLoaderMsgs.BABEL_REST_REQUEST_PAYLOAD, " Artifact Name: " + babelRequest.getArtifactName() + + " Artifact version: " + babelRequest.getArtifactVersion() + " Artifact payload: " + babelRequest.getCsar()); + } + + String resourceUrl = config.getBabelBaseUrl() + config.getBabelGenerateArtifactsUrl(); + + HttpHeaders headers = new HttpHeaders(); + headers.set(AaiRestClient.HEADER_TRANS_ID, transactionId); + headers.set(AaiRestClient.HEADER_FROM_APP_ID, AaiRestClient.ML_APP_NAME); + HttpEntity entity = new HttpEntity<>(babelRequest, headers); + + ResponseEntity> artifactResponse = restTemplate.exchange(resourceUrl, HttpMethod.POST, entity, new ParameterizedTypeReference>() {}); + + if (logger.isDebugEnabled()) { + logger.debug(ModelLoaderMsgs.DISTRIBUTION_EVENT, + "Babel response " + artifactResponse.getStatusCode() + " " + artifactResponse.getBody().toString()); + } + + if (!artifactResponse.getStatusCode().equals(HttpStatus.OK)) { + throw new BabelServiceClientException(artifactResponse.getBody().toString()); + } + + List babelArtifact = artifactResponse.getBody().stream() + .map(this::mapBabelDto) + .collect(Collectors.toList()); + + return babelArtifact; + } + + private BabelArtifact mapBabelDto(org.onap.aai.modelloader.babel.BabelArtifact babelDto) { + ArtifactType artifactType = babelDto.getType() == null + ? null + : ArtifactType.valueOf(babelDto.getType().name()); + return new BabelArtifact( + babelDto.getName(), + artifactType, + babelDto.getPayload()); + } +} diff --git a/src/main/java/org/onap/aai/modelloader/restclient/HttpsBabelServiceClient.java b/src/main/java/org/onap/aai/modelloader/restclient/HttpsBabelServiceClient.java deleted file mode 100644 index 0789996..0000000 --- a/src/main/java/org/onap/aai/modelloader/restclient/HttpsBabelServiceClient.java +++ /dev/null @@ -1,257 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017-2019 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017-2019 European Software Marketing Ltd. - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============LICENSE_END========================================================= - */ - -package org.onap.aai.modelloader.restclient; - -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; -import com.google.json.JsonSanitizer; -import com.sun.jersey.api.client.Client; // NOSONAR -import com.sun.jersey.api.client.ClientResponse; -import com.sun.jersey.api.client.WebResource; -import com.sun.jersey.api.client.config.DefaultClientConfig; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Base64; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; -import javax.ws.rs.core.Response; -import org.json.JSONException; -import org.json.JSONObject; -import org.onap.aai.babel.service.data.BabelArtifact; -import org.onap.aai.cl.api.LogFields; -import org.onap.aai.cl.api.LogLine; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; -import org.onap.aai.cl.mdc.MdcContext; -import org.onap.aai.cl.mdc.MdcOverride; -import org.onap.aai.modelloader.config.ModelLoaderConfig; -import org.onap.aai.modelloader.service.ModelLoaderMsgs; - -/** - * HTTPS Client for interfacing with Babel. - * - */ -public class HttpsBabelServiceClient implements BabelServiceClient { - - private static final Logger logger = LoggerFactory.getInstance().getLogger(HttpsBabelServiceClient.class); - private static final Logger metricsLogger = - LoggerFactory.getInstance().getMetricsLogger(HttpsBabelServiceClient.class); - private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); - - private static final String SSL_PROTOCOL = "TLS"; - private static final String KEYSTORE_ALGORITHM = "SunX509"; - private static final String KEYSTORE_TYPE = "PKCS12"; - - private final ModelLoaderConfig config; - private final Client client; - - /** - * @param config - * @throws NoSuchAlgorithmException - * @throws KeyStoreException - * @throws CertificateException - * @throws IOException - * @throws UnrecoverableKeyException - * @throws KeyManagementException - * @throws BabelServiceClientException - */ - public HttpsBabelServiceClient(ModelLoaderConfig config) - throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException, - UnrecoverableKeyException, KeyManagementException, BabelServiceClientException { - this.config = config; - - logger.debug(ModelLoaderMsgs.DISTRIBUTION_EVENT, "Creating Babel Service client"); - //Initialize SSL Context only if SSL is enabled - if (config.useHttpsWithBabel()) { - SSLContext ctx = SSLContext.getInstance(SSL_PROTOCOL); - KeyManagerFactory kmf = KeyManagerFactory.getInstance(KEYSTORE_ALGORITHM); - KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE); - - String clientCertPassword = config.getBabelKeyStorePassword(); - - char[] pwd = null; - if (clientCertPassword != null) { - pwd = clientCertPassword.toCharArray(); - } - - TrustManager[] trustManagers = getTrustManagers(); - - String clientCertFileName = config.getBabelKeyStorePath(); - if (clientCertFileName == null) { - ctx.init(null, trustManagers, null); - } else { - InputStream fin = Files.newInputStream(Paths.get(clientCertFileName)); - keyStore.load(fin, pwd); - kmf.init(keyStore, pwd); - ctx.init(kmf.getKeyManagers(), trustManagers, null); - } - - logger.debug(ModelLoaderMsgs.DISTRIBUTION_EVENT, "Initialised context"); - - HttpsURLConnection.setDefaultSSLSocketFactory(ctx.getSocketFactory()); - HttpsURLConnection.setDefaultHostnameVerifier((host, session) -> true); - } - - client = Client.create(new DefaultClientConfig()); - client.setConnectTimeout(config.getClientConnectTimeoutMs()); - client.setReadTimeout(config.getClientReadTimeoutMs()); - - logger.debug(ModelLoaderMsgs.DISTRIBUTION_EVENT, "Jersey client created"); - } - - private TrustManager[] getTrustManagers() throws NoSuchAlgorithmException, KeyStoreException, CertificateException, - IOException, BabelServiceClientException { - TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - // Using null here initializes the TMF with the default trust store. - tmf.init((KeyStore) null); - - // Create a new Trust Manager from the local trust store. - String trustStoreFile = config.getBabelTrustStorePath(); - if (trustStoreFile == null) { - throw new BabelServiceClientException("No Babel trust store defined"); - } - try (InputStream myKeys = Files.newInputStream(Paths.get(trustStoreFile))) { - KeyStore myTrustStore = KeyStore.getInstance(KeyStore.getDefaultType()); - myTrustStore.load(myKeys, config.getBabelTrustStorePassword().toCharArray()); - tmf.init(myTrustStore); - } - X509TrustManager localTm = findX509TrustManager(tmf); - - // Create a custom trust manager that wraps both our trust store and the default. - final X509TrustManager finalLocalTm = localTm; - - // Find the default trust manager. - final X509TrustManager defaultTrustManager = findX509TrustManager(tmf); - - return new TrustManager[] {new X509TrustManager() { - @Override - public X509Certificate[] getAcceptedIssuers() { - return defaultTrustManager.getAcceptedIssuers(); - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { - try { - finalLocalTm.checkServerTrusted(chain, authType); - } catch (CertificateException e) { // NOSONAR - defaultTrustManager.checkServerTrusted(chain, authType); - } - } - - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { - defaultTrustManager.checkClientTrusted(chain, authType); - } - }}; - } - - private X509TrustManager findX509TrustManager(TrustManagerFactory tmf) { - X509TrustManager trustManager = null; - for (TrustManager tm : tmf.getTrustManagers()) { - if (tm instanceof X509TrustManager) { - trustManager = (X509TrustManager) tm; - break; - } - } - return trustManager; - } - - /** - * @param artifactPayload - * @param artifactName - * @param artifactVersion - * @param transactionId - * @return - * @throws BabelServiceClientException - * @throws JSONException - */ - @Override - public List postArtifact(byte[] artifactPayload, String artifactName, String artifactVersion, - String transactionId) throws BabelServiceClientException { - Objects.requireNonNull(artifactPayload); - - String encodedPayload = Base64.getEncoder().encodeToString(artifactPayload); - - JSONObject obj = new JSONObject(); - try { - obj.put("csar", encodedPayload); - obj.put("artifactVersion", artifactVersion); - obj.put("artifactName", artifactName); - } catch (JSONException ex) { - throw new BabelServiceClientException(ex); - } - - if (logger.isInfoEnabled()) { - logger.info(ModelLoaderMsgs.BABEL_REST_REQUEST_PAYLOAD, " Artifact Name: " + artifactName - + " Artifact version: " + artifactVersion + " Artifact payload: " + encodedPayload); - } - - MdcOverride override = new MdcOverride(); - override.addAttribute(MdcContext.MDC_START_TIME, ZonedDateTime.now().format(formatter)); - - String resourceUrl = config.getBabelBaseUrl() + config.getBabelGenerateArtifactsUrl(); - WebResource webResource = client.resource(resourceUrl); - ClientResponse response = webResource.type("application/json") - .header(AaiRestClient.HEADER_TRANS_ID, transactionId) - .header(AaiRestClient.HEADER_FROM_APP_ID, AaiRestClient.ML_APP_NAME) - .post(ClientResponse.class, obj.toString()); - String sanitizedJson = JsonSanitizer.sanitize(response.getEntity(String.class)); - - if (logger.isDebugEnabled()) { - logger.debug(ModelLoaderMsgs.DISTRIBUTION_EVENT, - "Babel response " + response.getStatus() + " " + sanitizedJson); - } - - metricsLogger.info(ModelLoaderMsgs.BABEL_REST_REQUEST, new LogFields() // - .setField(LogLine.DefinedFields.TARGET_ENTITY, "Babel") - .setField(LogLine.DefinedFields.STATUS_CODE, - Response.Status.fromStatusCode(response.getStatus()).getFamily() - .equals(Response.Status.Family.SUCCESSFUL) ? "COMPLETE" : "ERROR") - .setField(LogLine.DefinedFields.RESPONSE_CODE, response.getStatus()) - .setField(LogLine.DefinedFields.RESPONSE_DESCRIPTION, response.getStatusInfo().toString()), // - override, response.toString()); - - if (response.getStatus() != Response.Status.OK.getStatusCode()) { - throw new BabelServiceClientException(sanitizedJson); - } - - return new Gson().fromJson(sanitizedJson, new TypeToken>() {}.getType()); - } -} diff --git a/src/main/java/org/onap/aai/modelloader/service/HttpsBabelServiceClientFactory.java b/src/main/java/org/onap/aai/modelloader/service/HttpsBabelServiceClientFactory.java deleted file mode 100644 index 2414991..0000000 --- a/src/main/java/org/onap/aai/modelloader/service/HttpsBabelServiceClientFactory.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017-2018 European Software Marketing Ltd. - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============LICENSE_END========================================================= - */ -package org.onap.aai.modelloader.service; - -import java.io.IOException; -import java.security.KeyManagementException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; -import org.onap.aai.modelloader.config.ModelLoaderConfig; -import org.onap.aai.modelloader.restclient.BabelServiceClient; -import org.onap.aai.modelloader.restclient.BabelServiceClientException; -import org.onap.aai.modelloader.restclient.HttpsBabelServiceClient; -import org.springframework.context.annotation.Primary; -import org.springframework.stereotype.Service; - -@Service -@Primary -public class HttpsBabelServiceClientFactory implements BabelServiceClientFactory { - - /* - * (non-Javadoc) - * - * @see org.onap.aai.modelloader.service.BabelServiceClientFactory#create(org.onap.aai.modelloader.config. - * ModelLoaderConfig) - */ - @Override - public BabelServiceClient create(ModelLoaderConfig config) throws BabelServiceClientException { - try { - return new HttpsBabelServiceClient(config); - } catch (UnrecoverableKeyException | KeyManagementException | NoSuchAlgorithmException | KeyStoreException - | CertificateException | IOException ex) { - throw new BabelServiceClientException(ex); - } - } - -} diff --git a/src/test/java/org/onap/aai/modelloader/BabelClientTestConfiguration.java b/src/test/java/org/onap/aai/modelloader/BabelClientTestConfiguration.java new file mode 100644 index 0000000..9df74fa --- /dev/null +++ b/src/test/java/org/onap/aai/modelloader/BabelClientTestConfiguration.java @@ -0,0 +1,57 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2024 Deutsche Telekom AG Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +package org.onap.aai.modelloader; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Properties; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +@TestConfiguration +public class BabelClientTestConfiguration { + @Value("${CONFIG_HOME}") + private String configDir; + + @Value("${wiremock.server.port}") + private int wiremockPort; + + @Primary + @Bean(name = "testProperties") + public Properties configProperties() throws IOException { + // Load model loader system configuration + InputStream configInputStream = Files.newInputStream(Paths.get(configDir, "model-loader.properties")); + Properties configProperties = new Properties(); + configProperties.load(configInputStream); + + setOverrides(configProperties); + + return configProperties; + } + + private void setOverrides(Properties configProperties) { + configProperties.setProperty("ml.babel.BASE_URL", "http://localhost:" + wiremockPort); + } +} diff --git a/src/test/java/org/onap/aai/modelloader/notification/ArtifactDownloadManagerVnfcTest.java b/src/test/java/org/onap/aai/modelloader/notification/ArtifactDownloadManagerVnfcTest.java index aa2d299..a64c00c 100644 --- a/src/test/java/org/onap/aai/modelloader/notification/ArtifactDownloadManagerVnfcTest.java +++ b/src/test/java/org/onap/aai/modelloader/notification/ArtifactDownloadManagerVnfcTest.java @@ -35,11 +35,12 @@ import java.util.Properties; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.onap.aai.babel.service.data.BabelArtifact; -import org.onap.aai.modelloader.config.ModelLoaderConfig; +import org.onap.aai.modelloader.babel.BabelArtifactService; import org.onap.aai.modelloader.entity.Artifact; import org.onap.aai.modelloader.entity.ArtifactType; import org.onap.aai.modelloader.entity.catalog.VnfCatalogArtifact; @@ -70,6 +71,7 @@ public class ArtifactDownloadManagerVnfcTest { @Mock private BabelArtifactConverter mockBabelArtifactConverter; @Mock private BabelServiceClientFactory mockClientFactory; @Mock private VnfCatalogExtractor mockVnfCatalogExtractor; + @InjectMocks private BabelArtifactService babelArtifactService; @BeforeEach public void setup() throws Exception { @@ -79,7 +81,7 @@ public class ArtifactDownloadManagerVnfcTest { Properties configProperties = new Properties(); configProperties.load(this.getClass().getClassLoader().getResourceAsStream("model-loader.properties")); downloadManager = new ArtifactDownloadManager(mockDistributionClient, - new ModelLoaderConfig(configProperties, "."), mockClientFactory, mockBabelArtifactConverter, mockNotificationPublisher, mockVnfCatalogExtractor); + mockNotificationPublisher, mockVnfCatalogExtractor, babelArtifactService); } @Test @@ -89,7 +91,7 @@ public class ArtifactDownloadManagerVnfcTest { IArtifactInfo artifactInfo = data.getServiceArtifacts().get(0); setupValidDownloadCsarMocks(data, artifactInfo); - when(mockBabelClient.postArtifact(any(), any(), any(), any())).thenReturn(createBabelArtifacts()); + when(mockBabelClient.postArtifact(any(), any())).thenReturn(createBabelArtifacts()); when(mockVnfCatalogExtractor.extract(any(), any())).thenReturn(new ArrayList<>()); List modelArtifacts = new ArrayList<>(); @@ -107,7 +109,7 @@ public class ArtifactDownloadManagerVnfcTest { IArtifactInfo artifactInfo = data.getServiceArtifacts().get(0); setupValidDownloadCsarMocks(data, artifactInfo); - when(mockBabelClient.postArtifact(any(), any(), any(), any())).thenReturn(createBabelArtifactsNoVnfc()); + when(mockBabelClient.postArtifact(any(), any())).thenReturn(createBabelArtifactsNoVnfc()); when(mockVnfCatalogExtractor.extract(any(), any())).thenReturn(createXmlVnfcArtifacts()); List modelArtifacts = new ArrayList<>(); @@ -125,7 +127,7 @@ public class ArtifactDownloadManagerVnfcTest { IArtifactInfo artifactInfo = data.getServiceArtifacts().get(0); setupValidDownloadCsarMocks(data, artifactInfo); - when(mockBabelClient.postArtifact(any(), any(), any(), any())).thenReturn(createBabelArtifactsNoVnfc()); + when(mockBabelClient.postArtifact(any(), any())).thenReturn(createBabelArtifactsNoVnfc()); when(mockVnfCatalogExtractor.extract(any(), any())).thenReturn(new ArrayList<>()); List modelArtifacts = new ArrayList<>(); @@ -143,7 +145,7 @@ public class ArtifactDownloadManagerVnfcTest { IArtifactInfo artifactInfo = data.getServiceArtifacts().get(0); setupValidDownloadCsarMocks(data, artifactInfo); - when(mockBabelClient.postArtifact(any(), any(), any(), any())).thenReturn(createBabelArtifacts()); + when(mockBabelClient.postArtifact(any(), any())).thenReturn(createBabelArtifacts()); when(mockVnfCatalogExtractor.extract(any(), any())).thenReturn(createXmlVnfcArtifacts()); doNothing().when(mockNotificationPublisher).publishDeployFailure(mockDistributionClient, data, artifactInfo); diff --git a/src/test/java/org/onap/aai/modelloader/notification/ModelArtifactHandlerTest.java b/src/test/java/org/onap/aai/modelloader/notification/ModelArtifactHandlerTest.java index b1269ee..4d7b53e 100644 --- a/src/test/java/org/onap/aai/modelloader/notification/ModelArtifactHandlerTest.java +++ b/src/test/java/org/onap/aai/modelloader/notification/ModelArtifactHandlerTest.java @@ -28,7 +28,6 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.deleteRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.client.WireMock.ok; import java.util.List; @@ -48,7 +47,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock; import org.springframework.http.HttpStatus; -import org.springframework.web.client.RestTemplate; import org.w3c.dom.Node; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDownloadManager.java b/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDownloadManager.java index 48b9a66..27d0aa9 100644 --- a/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDownloadManager.java +++ b/src/test/java/org/onap/aai/modelloader/notification/TestArtifactDownloadManager.java @@ -43,11 +43,12 @@ import org.hamcrest.collection.IsEmptyCollection; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.onap.aai.babel.service.data.BabelArtifact; -import org.onap.aai.modelloader.config.ModelLoaderConfig; +import org.onap.aai.modelloader.babel.BabelArtifactService; import org.onap.aai.modelloader.entity.Artifact; import org.onap.aai.modelloader.entity.model.BabelArtifactParsingException; import org.onap.aai.modelloader.extraction.VnfCatalogExtractor; @@ -73,6 +74,7 @@ public class TestArtifactDownloadManager { @Mock private NotificationPublisher mockNotificationPublisher; @Mock private BabelArtifactConverter mockBabelArtifactConverter; @Mock private BabelServiceClientFactory mockClientFactory; + @InjectMocks BabelArtifactService babelArtifactService; private VnfCatalogExtractor vnfCatalogExtractor; @BeforeEach @@ -84,7 +86,7 @@ public class TestArtifactDownloadManager { Properties configProperties = new Properties(); configProperties.load(this.getClass().getClassLoader().getResourceAsStream("model-loader.properties")); downloadManager = new ArtifactDownloadManager(mockDistributionClient, - new ModelLoaderConfig(configProperties, "."), mockClientFactory, mockBabelArtifactConverter, mockNotificationPublisher, vnfCatalogExtractor); + mockNotificationPublisher, vnfCatalogExtractor, babelArtifactService); } @AfterEach @@ -142,8 +144,6 @@ public class TestArtifactDownloadManager { Mockito.verify(mockDistributionClient).download(artifactInfo); Mockito.verify(mockNotificationPublisher).publishDownloadSuccess(mockDistributionClient, data, artifactInfo); Mockito.verify(mockNotificationPublisher).publishDeployFailure(mockDistributionClient, data, artifactInfo); - - Mockito.verifyNoInteractions(mockBabelArtifactConverter); } @Test @@ -153,14 +153,14 @@ public class TestArtifactDownloadManager { when(mockDistributionClient.download(artifact)).thenReturn(createDistributionClientDownloadResult( DistributionActionResultEnum.SUCCESS, null, "This is not a valid Tosca CSAR File".getBytes())); doNothing().when(mockNotificationPublisher).publishDownloadSuccess(mockDistributionClient, data, artifact); - when(mockBabelClient.postArtifact(any(), any(), any(), any())).thenThrow(new BabelServiceClientException("")); + when(mockBabelClient.postArtifact(any(), any())).thenThrow(new BabelServiceClientException("")); doNothing().when(mockNotificationPublisher).publishDeployFailure(mockDistributionClient, data, artifact); assertThat(downloadManager.downloadArtifacts(data, data.getServiceArtifacts(), null, null), is(false)); Mockito.verify(mockDistributionClient).download(artifact); Mockito.verify(mockNotificationPublisher).publishDownloadSuccess(mockDistributionClient, data, artifact); - Mockito.verify(mockBabelClient).postArtifact(any(), any(), any(), any()); + Mockito.verify(mockBabelClient).postArtifact(any(), any()); Mockito.verify(mockNotificationPublisher).publishDeployFailure(mockDistributionClient, data, artifact); Mockito.verifyNoInteractions(mockBabelArtifactConverter); @@ -204,7 +204,7 @@ public class TestArtifactDownloadManager { Mockito.verify(mockDistributionClient).download(artifactInfo); Mockito.verify(mockNotificationPublisher).publishDownloadSuccess(mockDistributionClient, data, artifactInfo); - Mockito.verify(mockBabelClient).postArtifact(any(), any(), any(), any()); + Mockito.verify(mockBabelClient).postArtifact(any(), any()); Mockito.verify(mockBabelArtifactConverter).convertToModel(any()); Mockito.verify(mockBabelArtifactConverter).convertToCatalog(any()); } @@ -214,7 +214,7 @@ public class TestArtifactDownloadManager { when(mockDistributionClient.download(artifactInfo)) .thenReturn(createDistributionClientDownloadResult(DistributionActionResultEnum.SUCCESS, null, artifactTestUtils.loadResource("compressedArtifacts/service-VscpaasTest-csar.csar"))); - when(mockBabelClient.postArtifact(any(), any(), any(), any())).thenReturn(createBabelArtifacts()); + when(mockBabelClient.postArtifact(any(), any())).thenReturn(createBabelArtifacts()); } private List createBabelArtifacts() { @@ -275,7 +275,7 @@ public class TestArtifactDownloadManager { Mockito.verify(mockDistributionClient).download(serviceArtifact); Mockito.verify(mockNotificationPublisher).publishDownloadSuccess(mockDistributionClient, data, serviceArtifact); - Mockito.verify(mockBabelClient).postArtifact(any(), any(), any(), any()); + Mockito.verify(mockBabelClient).postArtifact(any(), any()); Mockito.verify(mockBabelArtifactConverter).convertToModel(any()); Mockito.verify(mockBabelArtifactConverter).convertToCatalog(any()); @@ -298,7 +298,7 @@ public class TestArtifactDownloadManager { when(mockBabelArtifactConverter.convertToModel(anyList())) .thenThrow(BabelArtifactParsingException.class); doNothing().when(mockNotificationPublisher).publishDeployFailure(mockDistributionClient, data, artifactInfo); - when(mockBabelClient.postArtifact(any(), any(), any(), any())).thenReturn(createBabelArtifacts()); + when(mockBabelClient.postArtifact(any(), any())).thenReturn(createBabelArtifacts()); List modelArtifacts = new ArrayList<>(); List catalogFiles = new ArrayList<>(); @@ -309,7 +309,7 @@ public class TestArtifactDownloadManager { Mockito.verify(mockDistributionClient).download(artifactInfo); Mockito.verify(mockNotificationPublisher).publishDeployFailure(mockDistributionClient, data, artifactInfo); - Mockito.verify(mockBabelClient).postArtifact(any(), any(), any(), any()); + Mockito.verify(mockBabelClient).postArtifact(any(), any()); Mockito.verify(mockBabelArtifactConverter).convertToModel(any()); } diff --git a/src/test/java/org/onap/aai/modelloader/restclient/MockBabelServiceClient.java b/src/test/java/org/onap/aai/modelloader/restclient/MockBabelServiceClient.java index 604aca7..783ad2e 100644 --- a/src/test/java/org/onap/aai/modelloader/restclient/MockBabelServiceClient.java +++ b/src/test/java/org/onap/aai/modelloader/restclient/MockBabelServiceClient.java @@ -23,6 +23,7 @@ package org.onap.aai.modelloader.restclient; import java.util.Collections; import java.util.List; import org.onap.aai.babel.service.data.BabelArtifact; +import org.onap.aai.babel.service.data.BabelRequest; import org.onap.aai.modelloader.config.ModelLoaderConfig; /** @@ -34,8 +35,8 @@ public class MockBabelServiceClient implements BabelServiceClient { public MockBabelServiceClient(ModelLoaderConfig config) throws BabelServiceClientException {} @Override - public List postArtifact(byte[] artifactPayload, String artifactName, String artifactVersion, - String transactionId) throws BabelServiceClientException { + public List postArtifact(BabelRequest babelRequest, String transactionId) + throws BabelServiceClientException { return Collections.emptyList(); } } diff --git a/src/test/java/org/onap/aai/modelloader/restclient/TestBabelServiceClient.java b/src/test/java/org/onap/aai/modelloader/restclient/TestBabelServiceClient.java index 169943c..d82bff0 100644 --- a/src/test/java/org/onap/aai/modelloader/restclient/TestBabelServiceClient.java +++ b/src/test/java/org/onap/aai/modelloader/restclient/TestBabelServiceClient.java @@ -28,34 +28,40 @@ import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.Base64; import java.util.List; -import java.util.Properties; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.onap.aai.babel.service.data.BabelArtifact; -import org.onap.aai.modelloader.config.ModelLoaderConfig; -import org.onap.aai.modelloader.service.HttpsBabelServiceClientFactory; +import org.onap.aai.babel.service.data.BabelRequest; +import org.onap.aai.modelloader.BabelClientTestConfiguration; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock; +import org.springframework.context.annotation.Import; +import org.springframework.test.annotation.DirtiesContext; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.tomakehurst.wiremock.client.WireMock; -import com.github.tomakehurst.wiremock.matching.EqualToPattern; /** * Local testing of the Babel service client. * */ @SpringBootTest +@DirtiesContext @AutoConfigureWireMock(port = 0) +@Import(BabelClientTestConfiguration.class) public class TestBabelServiceClient { @Value("${wiremock.server.port}") private int wiremockPort; + @Autowired BabelServiceClient client; + @BeforeAll public static void setup() throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); @@ -64,7 +70,7 @@ public class TestBabelServiceClient { new BabelArtifact("art2", null, ""), new BabelArtifact("art3", null, "")); WireMock.stubFor( - WireMock.post(WireMock.urlEqualTo("/generate")) + WireMock.post(WireMock.urlEqualTo("/services/babel-service/v1/app/generateArtifacts")) .withHeader("X-TransactionId", WireMock.equalTo("Test-Transaction-ID-BabelClient")) .withHeader("X-FromAppId", WireMock.equalTo("ModelLoader")) .withRequestBody(WireMock.matchingJsonPath("$.artifactName", WireMock.equalTo("service-Vscpass-Test"))) @@ -78,39 +84,13 @@ public class TestBabelServiceClient { @Test public void testRestClient() throws BabelServiceClientException, IOException, URISyntaxException { - String url = "http://localhost:" + wiremockPort; - Properties configProperties = new Properties(); - configProperties.put("ml.babel.KEYSTORE_PASSWORD", "OBF:1vn21ugu1saj1v9i1v941sar1ugw1vo0"); - configProperties.put("ml.babel.KEYSTORE_FILE", "src/test/resources/auth/aai-client-dummy.p12"); - configProperties.put("ml.babel.TRUSTSTORE_PASSWORD", "OBF:1vn21ugu1saj1v9i1v941sar1ugw1vo0"); - // In a real deployment this would be a different file (to the client keystore) - configProperties.put("ml.babel.TRUSTSTORE_FILE", "src/test/resources/auth/aai-client-dummy.p12"); - configProperties.put("ml.babel.BASE_URL", url); - configProperties.put("ml.babel.GENERATE_ARTIFACTS_URL", "/generate"); - configProperties.put("ml.aai.RESTCLIENT_CONNECT_TIMEOUT", "12000"); - configProperties.put("ml.aai.RESTCLIENT_READ_TIMEOUT", "12000"); - BabelServiceClient client = - new HttpsBabelServiceClientFactory().create(new ModelLoaderConfig(configProperties, ".")); - List result = - client.postArtifact(readBytesFromFile("compressedArtifacts/service-VscpaasTest-csar.csar"), - "service-Vscpass-Test", "1.0", "Test-Transaction-ID-BabelClient"); - assertThat(result.size(), is(equalTo(3))); - } + BabelRequest babelRequest = new BabelRequest(); + babelRequest.setArtifactName("service-Vscpass-Test"); + babelRequest.setCsar(Base64.getEncoder().encodeToString(readBytesFromFile("compressedArtifacts/service-VscpaasTest-csar.csar"))); + babelRequest.setArtifactVersion("1.0"); - @Test - public void testRestClientHttp() throws BabelServiceClientException, IOException, URISyntaxException { - String url = "http://localhost:" + wiremockPort; - Properties configProperties = new Properties(); - configProperties.put("ml.babel.USE_HTTPS", "false"); - configProperties.put("ml.babel.BASE_URL", url); - configProperties.put("ml.babel.GENERATE_ARTIFACTS_URL", "/generate"); - configProperties.put("ml.aai.RESTCLIENT_CONNECT_TIMEOUT", "3000"); - configProperties.put("ml.aai.RESTCLIENT_READ_TIMEOUT", "3000"); - BabelServiceClient client = - new HttpsBabelServiceClientFactory().create(new ModelLoaderConfig(configProperties, ".")); List result = - client.postArtifact(readBytesFromFile("compressedArtifacts/service-VscpaasTest-csar.csar"), - "service-Vscpass-Test", "1.0", "Test-Transaction-ID-BabelClient"); + client.postArtifact(babelRequest, "Test-Transaction-ID-BabelClient"); assertThat(result.size(), is(equalTo(3))); } diff --git a/src/test/java/org/onap/aai/modelloader/service/TestModelController.java b/src/test/java/org/onap/aai/modelloader/service/TestModelController.java index 9d7d5ea..970aa7a 100644 --- a/src/test/java/org/onap/aai/modelloader/service/TestModelController.java +++ b/src/test/java/org/onap/aai/modelloader/service/TestModelController.java @@ -33,7 +33,9 @@ import javax.ws.rs.core.Response; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; import org.mockito.Mock; +import org.onap.aai.modelloader.babel.BabelArtifactService; import org.onap.aai.modelloader.config.ModelLoaderConfig; import org.onap.aai.modelloader.extraction.VnfCatalogExtractor; import org.onap.aai.modelloader.notification.ArtifactDownloadManager; @@ -64,14 +66,15 @@ public class TestModelController { @Mock BabelServiceClientFactory clientFactory; @Mock BabelServiceClient babelServiceClient; + @InjectMocks BabelArtifactService babelArtifactService; private ModelController modelController; @BeforeEach public void init() throws BabelServiceClientException { when(clientFactory.create(any())).thenReturn(babelServiceClient); - when(babelServiceClient.postArtifact(any(), any(), any(), any())).thenReturn(Collections.emptyList()); - ArtifactDownloadManager artifactDownloadManager = new ArtifactDownloadManager(iDistributionClient, modelLoaderConfig, clientFactory, babelArtifactConverter, notificationPublisher, vnfCatalogExtractor); + when(babelServiceClient.postArtifact(any(), any())).thenReturn(Collections.emptyList()); + ArtifactDownloadManager artifactDownloadManager = new ArtifactDownloadManager(iDistributionClient, notificationPublisher, vnfCatalogExtractor, babelArtifactService); this.modelController = new ModelController(iDistributionClient, modelLoaderConfig, artifactDeploymentManager, artifactDownloadManager); } -- 2.16.6 From 8204d232f8cc37e28561ee18b60806c7b3f5f783 Mon Sep 17 00:00:00 2001 From: Fiete Ostkamp Date: Thu, 18 Apr 2024 17:05:19 +0200 Subject: [PATCH 16/16] Update babel dependency in model-loader Issue-ID: AAI-3832 Change-Id: I1cd1588dac16401c7f5064398ff7b8de71bffe30 Signed-off-by: Fiete Ostkamp --- pom.xml | 2 +- .../modelloader/restclient/BabelServiceClientImpl.java | 18 ++---------------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/pom.xml b/pom.xml index 2efcd39..1a3028c 100644 --- a/pom.xml +++ b/pom.xml @@ -64,7 +64,7 @@ 1.22 2.10.1 1.3 - 1.9.5 + 1.13.0 1.2.1 2.1.1 1.2.11 diff --git a/src/main/java/org/onap/aai/modelloader/restclient/BabelServiceClientImpl.java b/src/main/java/org/onap/aai/modelloader/restclient/BabelServiceClientImpl.java index 5400892..53ce917 100644 --- a/src/main/java/org/onap/aai/modelloader/restclient/BabelServiceClientImpl.java +++ b/src/main/java/org/onap/aai/modelloader/restclient/BabelServiceClientImpl.java @@ -69,7 +69,7 @@ public class BabelServiceClientImpl implements BabelServiceClient { headers.set(AaiRestClient.HEADER_FROM_APP_ID, AaiRestClient.ML_APP_NAME); HttpEntity entity = new HttpEntity<>(babelRequest, headers); - ResponseEntity> artifactResponse = restTemplate.exchange(resourceUrl, HttpMethod.POST, entity, new ParameterizedTypeReference>() {}); + ResponseEntity> artifactResponse = restTemplate.exchange(resourceUrl, HttpMethod.POST, entity, new ParameterizedTypeReference>() {}); if (logger.isDebugEnabled()) { logger.debug(ModelLoaderMsgs.DISTRIBUTION_EVENT, @@ -80,20 +80,6 @@ public class BabelServiceClientImpl implements BabelServiceClient { throw new BabelServiceClientException(artifactResponse.getBody().toString()); } - List babelArtifact = artifactResponse.getBody().stream() - .map(this::mapBabelDto) - .collect(Collectors.toList()); - - return babelArtifact; - } - - private BabelArtifact mapBabelDto(org.onap.aai.modelloader.babel.BabelArtifact babelDto) { - ArtifactType artifactType = babelDto.getType() == null - ? null - : ArtifactType.valueOf(babelDto.getType().name()); - return new BabelArtifact( - babelDto.getName(), - artifactType, - babelDto.getPayload()); + return artifactResponse.getBody(); } } -- 2.16.6