Add support to upload deployment helm artifacts to chartmuseum 30/12530/1
authoraravind.est <aravindhan.a@est.tech>
Wed, 14 Feb 2024 14:26:41 +0000 (14:26 +0000)
committeraravind.est <aravindhan.a@est.tech>
Wed, 14 Feb 2024 15:39:07 +0000 (15:39 +0000)
HELM artifacts can be defined as part of the rApp package.
These artifacts will be uploaded to the chartmuseum server as part of the rApp priming process.

Issue-ID: NONRTRIC-975
Change-Id: Ia65e7756cc76763d25362ac1f5261785e7a65d18
Signed-off-by: aravind.est <aravindhan.a@est.tech>
15 files changed:
rapp-manager-application/src/main/java/com/oransc/rappmanager/service/DeploymentArtifactsService.java [new file with mode: 0755]
rapp-manager-application/src/main/java/com/oransc/rappmanager/service/RappService.java
rapp-manager-application/src/test/java/com/oransc/rappmanager/service/DeploymentArtifactsServiceTest.java [new file with mode: 0755]
rapp-manager-application/src/test/java/com/oransc/rappmanager/service/RappServiceTest.java
rapp-manager-application/src/test/resources/invalid-rapp-package.csar
rapp-manager-application/src/test/resources/valid-rapp-package.csar
rapp-manager-models/src/main/java/com/oransc/rappmanager/models/csar/DeploymentItem.java [new file with mode: 0755]
rapp-manager-models/src/main/java/com/oransc/rappmanager/models/csar/DeploymentItemArtifactType.java [new file with mode: 0755]
rapp-manager-models/src/main/java/com/oransc/rappmanager/models/csar/DeploymentItemTargetServer.java [new file with mode: 0755]
rapp-manager-models/src/main/java/com/oransc/rappmanager/models/csar/RappCsarConfigurationHandler.java
rapp-manager-models/src/test/java/com/oransc/rappmanager/models/csar/RappCsarConfigurationHandlerTest.java
rapp-manager-models/src/test/resources/invalid-rapp-package-empty-asd-yaml.csar [new file with mode: 0755]
rapp-manager-models/src/test/resources/valid-rapp-package.csar
sample-rapp-generator/rapp-all/Definitions/asd.yaml
sample-rapp-generator/rapp-all/Definitions/asd_types.yaml

diff --git a/rapp-manager-application/src/main/java/com/oransc/rappmanager/service/DeploymentArtifactsService.java b/rapp-manager-application/src/main/java/com/oransc/rappmanager/service/DeploymentArtifactsService.java
new file mode 100755 (executable)
index 0000000..aaafdb2
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * ============LICENSE_START======================================================================
+ * Copyright (C) 2024 OpenInfra Foundation Europe. 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 com.oransc.rappmanager.service;
+
+import com.oransc.rappmanager.models.csar.DeploymentItem;
+import com.oransc.rappmanager.models.csar.DeploymentItemArtifactType;
+import com.oransc.rappmanager.models.csar.RappCsarConfigurationHandler;
+import com.oransc.rappmanager.models.exception.RappHandlerException;
+import com.oransc.rappmanager.models.rapp.Rapp;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestTemplate;
+
+@Service
+@RequiredArgsConstructor
+public class DeploymentArtifactsService {
+
+    private final RestTemplate restTemplate;
+    private final RappCsarConfigurationHandler rappCsarConfigurationHandler;
+
+    public boolean configureDeploymentArtifacts(Rapp rapp) {
+        List<DeploymentItem> deploymentItems = rappCsarConfigurationHandler.getDeploymentItems(rapp);
+        return deploymentItems.stream().filter(deploymentItem -> deploymentItem.getArtifactType()
+                                                                         .equals(DeploymentItemArtifactType.HELMCHART))
+                       .allMatch(deploymentItem -> uploadHelmChart(rapp, deploymentItem));
+    }
+
+    boolean uploadHelmChart(Rapp rApp, DeploymentItem deploymentItem) throws RappHandlerException {
+        try {
+            HttpHeaders httpHeaders = new HttpHeaders();
+            httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
+            HttpEntity<ByteArrayResource> requestHttpEntity =
+                    new HttpEntity<>(rappCsarConfigurationHandler.getArtifactPayload(rApp, deploymentItem.getFile()),
+                            httpHeaders);
+            ResponseEntity<String> responseEntity =
+                    restTemplate.exchange(deploymentItem.getTargetServerUri(), HttpMethod.POST, requestHttpEntity,
+                            String.class);
+            if (responseEntity.getStatusCode().is2xxSuccessful()) {
+                return true;
+            }
+        } catch (HttpClientErrorException exception) {
+            if (exception.getStatusCode().equals(HttpStatus.CONFLICT)) {
+                return true;
+            }
+        } catch (Exception e) {
+            throw new RappHandlerException(HttpStatus.BAD_REQUEST,
+                    String.format("Unable to connect to the chartmuseum server %s to upload helm artifact %s",
+                            deploymentItem.getTargetServerUri(), deploymentItem.getFile()));
+        }
+        return false;
+    }
+}
index 8e71d50..23ab2d8 100755 (executable)
@@ -44,13 +44,19 @@ public class RappService {
     private final List<RappDeployer> rappDeployers;
     private final RappInstanceStateMachine rappInstanceStateMachine;
     private final RappCacheService rappCacheService;
+    private final DeploymentArtifactsService deploymentArtifactsService;
     private static final String STATE_TRANSITION_NOT_PERMITTED = "State transition from %s to %s is not permitted.";
 
     public ResponseEntity<String> primeRapp(Rapp rapp) {
         if (rapp.getState().equals(RappState.COMMISSIONED)) {
             rapp.setState(RappState.PRIMING);
             rapp.setReason(null);
-            if (rappDeployers.parallelStream().allMatch(rappDeployer -> rappDeployer.primeRapp(rapp))) {
+            //Configuring the deployment artifact needs to be done before starting the priming with other components
+            //If there are additional conditions needs to be checked before priming, This needs handled as part of pre-priming stage.
+            if (deploymentArtifactsService.configureDeploymentArtifacts(rapp) && rappDeployers.parallelStream()
+                                                                                         .allMatch(
+                                                                                                 rappDeployer -> rappDeployer.primeRapp(
+                                                                                                         rapp))) {
                 rapp.setState(RappState.PRIMED);
                 return ResponseEntity.ok().build();
             }
diff --git a/rapp-manager-application/src/test/java/com/oransc/rappmanager/service/DeploymentArtifactsServiceTest.java b/rapp-manager-application/src/test/java/com/oransc/rappmanager/service/DeploymentArtifactsServiceTest.java
new file mode 100755 (executable)
index 0000000..4a3a08a
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * ============LICENSE_START======================================================================
+ * Copyright (C) 2024 OpenInfra Foundation Europe. 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 com.oransc.rappmanager.service;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withException;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withServerError;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withTooManyRequests;
+
+import com.oransc.rappmanager.models.csar.DeploymentItem;
+import com.oransc.rappmanager.models.csar.RappCsarConfigurationHandler;
+import com.oransc.rappmanager.models.exception.RappHandlerException;
+import com.oransc.rappmanager.models.rapp.Rapp;
+import com.oransc.rappmanager.models.rapp.RappState;
+import com.oransc.rappmanager.sme.service.SmeLifecycleManager;
+import java.io.IOException;
+import java.util.List;
+import java.util.UUID;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.boot.test.mock.mockito.SpyBean;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.test.web.client.ExpectedCount;
+import org.springframework.test.web.client.MockRestServiceServer;
+import org.springframework.web.client.RestTemplate;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+class DeploymentArtifactsServiceTest {
+
+    @SpyBean
+    DeploymentArtifactsService deploymentArtifactsService;
+    @Autowired
+    RestTemplate restTemplate;
+    @Autowired
+    RappCsarConfigurationHandler rappCsarConfigurationHandler;
+    @MockBean
+    SmeLifecycleManager smeLifecycleManager;
+
+    MockRestServiceServer mockServer;
+
+    String validCsarFileLocation = "src/test/resources/";
+
+    private final String validRappFile = "valid-rapp-package.csar";
+
+    @BeforeEach
+    public void init() {
+        mockServer = MockRestServiceServer.createServer(restTemplate);
+    }
+
+    @ParameterizedTest
+    @EnumSource(value = HttpStatus.class, names = {"CREATED", "CONFLICT"})
+    void testChartUpload(HttpStatus status) {
+        Rapp rapp = Rapp.builder().rappId(UUID.randomUUID()).name("").packageName(validRappFile)
+                            .packageLocation(validCsarFileLocation).state(RappState.COMMISSIONED).build();
+        List<DeploymentItem> deploymentItems = rappCsarConfigurationHandler.getDeploymentItems(rapp);
+        deploymentItems.forEach(deploymentItem -> mockServer.expect(ExpectedCount.once(),
+                        requestTo(deploymentItem.getTargetServerUri())).andExpect(method(HttpMethod.POST))
+                                                          .andRespond(withStatus(status)));
+        assertTrue(deploymentArtifactsService.configureDeploymentArtifacts(rapp));
+        mockServer.verify();
+    }
+
+    @Test
+    void testChartUploadNoArtifacts() {
+        String invalidRappFile = "invalid-rapp-package.csar";
+        Rapp rapp = Rapp.builder().rappId(UUID.randomUUID()).name("").packageName(invalidRappFile)
+                            .packageLocation(validCsarFileLocation).state(RappState.COMMISSIONED).build();
+        assertTrue(deploymentArtifactsService.configureDeploymentArtifacts(rapp));
+    }
+
+    @Test
+    void testChartUploadFailure() {
+        Rapp rapp = Rapp.builder().rappId(UUID.randomUUID()).name("").packageName(validRappFile)
+                            .packageLocation(validCsarFileLocation).state(RappState.COMMISSIONED).build();
+        List<DeploymentItem> deploymentItems = rappCsarConfigurationHandler.getDeploymentItems(rapp);
+        deploymentItems.stream().findFirst().ifPresent(deploymentItem -> mockServer.expect(ExpectedCount.once(),
+                        requestTo(deploymentItem.getTargetServerUri())).andExpect(method(HttpMethod.POST))
+                                                                                 .andRespond(withServerError()));
+        RappHandlerException exception = assertThrows(RappHandlerException.class,
+                () -> deploymentArtifactsService.configureDeploymentArtifacts(rapp));
+        assertEquals(HttpStatus.BAD_REQUEST, exception.getStatusCode());
+        mockServer.verify();
+    }
+
+    @Test
+    void testChartUploadFailureWithNotFound() {
+        Rapp rapp = Rapp.builder().rappId(UUID.randomUUID()).name("").packageName(validRappFile)
+                            .packageLocation(validCsarFileLocation).state(RappState.COMMISSIONED).build();
+        List<DeploymentItem> deploymentItems = rappCsarConfigurationHandler.getDeploymentItems(rapp);
+        deploymentItems.stream().findFirst().ifPresent(deploymentItem -> mockServer.expect(ExpectedCount.once(),
+                requestTo(deploymentItem.getTargetServerUri())).andExpect(method(HttpMethod.POST)).andRespond(
+                withStatus(HttpStatus.NOT_FOUND)));
+        assertFalse(deploymentArtifactsService.configureDeploymentArtifacts(rapp));
+        mockServer.verify();
+    }
+
+    @Test
+    void testChartUploadFailureWithException() {
+        Rapp rapp = Rapp.builder().rappId(UUID.randomUUID()).name("").packageName(validRappFile)
+                            .packageLocation(validCsarFileLocation).state(RappState.COMMISSIONED).build();
+        List<DeploymentItem> deploymentItems = rappCsarConfigurationHandler.getDeploymentItems(rapp);
+        deploymentItems.stream().findFirst().ifPresent(deploymentItem -> mockServer.expect(ExpectedCount.once(),
+                requestTo(deploymentItem.getTargetServerUri())).andExpect(method(HttpMethod.POST)).andRespond(
+                withException(new IOException())));
+        RappHandlerException exception = assertThrows(RappHandlerException.class,
+                () -> deploymentArtifactsService.configureDeploymentArtifacts(rapp));
+        assertEquals(HttpStatus.BAD_REQUEST, exception.getStatusCode());
+        mockServer.verify();
+    }
+
+    @Test
+    void testChartUploadFailureWithTooManyRequests() {
+        Rapp rapp = Rapp.builder().rappId(UUID.randomUUID()).name("").packageName(validRappFile)
+                            .packageLocation(validCsarFileLocation).state(RappState.COMMISSIONED).build();
+        List<DeploymentItem> deploymentItems = rappCsarConfigurationHandler.getDeploymentItems(rapp);
+        deploymentItems.stream().findFirst().ifPresent(deploymentItem -> mockServer.expect(ExpectedCount.once(),
+                        requestTo(deploymentItem.getTargetServerUri())).andExpect(method(HttpMethod.POST))
+                                                                                 .andRespond(withTooManyRequests()));
+        assertFalse(deploymentArtifactsService.configureDeploymentArtifacts(rapp));
+        mockServer.verify();
+    }
+}
index 4154b6c..9dc4987 100755 (executable)
@@ -60,6 +60,9 @@ class RappServiceTest {
     @MockBean
     DmeDeployer dmeDeployer;
 
+    @MockBean
+    DeploymentArtifactsService deploymentArtifactsService;
+
     @MockBean
     SmeLifecycleManager smeLifecycleManager;
 
@@ -80,6 +83,7 @@ class RappServiceTest {
         when(acmDeployer.primeRapp(any())).thenReturn(true);
         when(dmeDeployer.primeRapp(any())).thenReturn(true);
         when(smeDeployer.primeRapp(any())).thenReturn(true);
+        when(deploymentArtifactsService.configureDeploymentArtifacts(any())).thenReturn(true);
         assertEquals(HttpStatus.OK, rappService.primeRapp(rapp).getStatusCode());
         assertEquals(RappState.PRIMED, rapp.getState());
     }
@@ -100,6 +104,7 @@ class RappServiceTest {
     void testPrimeRappAcmFailure() {
         Rapp rapp = Rapp.builder().rappId(UUID.randomUUID()).name("").packageName(validRappFile)
                             .packageLocation(validCsarFileLocation).state(RappState.COMMISSIONED).build();
+        when(deploymentArtifactsService.configureDeploymentArtifacts(any())).thenReturn(true);
         when(acmDeployer.primeRapp(any())).thenReturn(false);
         when(dmeDeployer.primeRapp(any())).thenReturn(true);
         RappHandlerException rappHandlerException =
@@ -112,6 +117,7 @@ class RappServiceTest {
     void testPrimeRappDmeFailure() {
         Rapp rapp = Rapp.builder().rappId(UUID.randomUUID()).name("").packageName(validRappFile)
                             .packageLocation(validCsarFileLocation).state(RappState.COMMISSIONED).build();
+        when(deploymentArtifactsService.configureDeploymentArtifacts(any())).thenReturn(true);
         when(acmDeployer.primeRapp(any())).thenReturn(true);
         when(dmeDeployer.primeRapp(any())).thenReturn(false);
         RappHandlerException rappHandlerException =
@@ -120,6 +126,16 @@ class RappServiceTest {
         assertEquals(RappState.COMMISSIONED, rapp.getState());
     }
 
+    @Test
+    void testPrimeRappDeployArtifactFailure() {
+        Rapp rapp = Rapp.builder().rappId(UUID.randomUUID()).name("").packageName(validRappFile)
+                            .packageLocation(validCsarFileLocation).state(RappState.COMMISSIONED).build();
+        when(deploymentArtifactsService.configureDeploymentArtifacts(any())).thenReturn(false);
+        RappHandlerException rappHandlerException =
+                assertThrows(RappHandlerException.class, () -> rappService.primeRapp(rapp));
+        assertEquals(HttpStatus.BAD_GATEWAY, rappHandlerException.getStatusCode());
+        assertEquals(RappState.COMMISSIONED, rapp.getState());
+    }
 
     @Test
     void testDeprimeRapp() {
index dc18b5b..97efcb5 100755 (executable)
Binary files a/rapp-manager-application/src/test/resources/invalid-rapp-package.csar and b/rapp-manager-application/src/test/resources/invalid-rapp-package.csar differ
index ffe2f4b..667317e 100755 (executable)
Binary files a/rapp-manager-application/src/test/resources/valid-rapp-package.csar and b/rapp-manager-application/src/test/resources/valid-rapp-package.csar differ
diff --git a/rapp-manager-models/src/main/java/com/oransc/rappmanager/models/csar/DeploymentItem.java b/rapp-manager-models/src/main/java/com/oransc/rappmanager/models/csar/DeploymentItem.java
new file mode 100755 (executable)
index 0000000..6e4a121
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * ============LICENSE_START======================================================================
+ * Copyright (C) 2024 OpenInfra Foundation Europe. 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 com.oransc.rappmanager.models.csar;
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder(toBuilder = true)
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+public class DeploymentItem {
+
+    String file;
+    DeploymentItemArtifactType artifactType;
+    String itemId;
+    DeploymentItemTargetServer targetServer;
+    String targetServerUri;
+}
diff --git a/rapp-manager-models/src/main/java/com/oransc/rappmanager/models/csar/DeploymentItemArtifactType.java b/rapp-manager-models/src/main/java/com/oransc/rappmanager/models/csar/DeploymentItemArtifactType.java
new file mode 100755 (executable)
index 0000000..46f007d
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * ============LICENSE_START======================================================================
+ * Copyright (C) 2024 OpenInfra Foundation Europe. 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 com.oransc.rappmanager.models.csar;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public enum DeploymentItemArtifactType {
+    @JsonProperty("helm_chart") HELMCHART;
+}
diff --git a/rapp-manager-models/src/main/java/com/oransc/rappmanager/models/csar/DeploymentItemTargetServer.java b/rapp-manager-models/src/main/java/com/oransc/rappmanager/models/csar/DeploymentItemTargetServer.java
new file mode 100755 (executable)
index 0000000..2bc91e9
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * ============LICENSE_START======================================================================
+ * Copyright (C) 2024 OpenInfra Foundation Europe. 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 com.oransc.rappmanager.models.csar;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public enum DeploymentItemTargetServer {
+
+    @JsonProperty("chartmuseum") CHARTMUSEUM;
+}
index 1def6e5..9e2f953 100755 (executable)
@@ -22,6 +22,7 @@ package com.oransc.rappmanager.models.csar;
 import static com.google.common.base.Splitter.on;
 import static com.google.common.collect.Iterables.filter;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.gson.Gson;
@@ -35,6 +36,7 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 import java.util.UUID;
@@ -47,6 +49,7 @@ import org.apache.commons.compress.archivers.ArchiveEntry;
 import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.core.io.ByteArrayResource;
 import org.springframework.http.HttpStatus;
 import org.springframework.stereotype.Service;
 import org.springframework.web.multipart.MultipartFile;
@@ -71,6 +74,8 @@ public class RappCsarConfigurationHandler {
     private static final String DME_CONSUMER_INFO_TYPES_LOCATION = "Files/Dme/consumerinfotypes";
     private static final String DME_INFO_PRODUCERS_LOCATION = "Files/Dme/infoproducers";
     private static final String DME_INFO_CONSUMERS_LOCATION = "Files/Dme/infoconsumers";
+    private static final String ARTIFACTS_LOCATION_JSON_POINTER =
+            "/topology_template/node_templates/applicationServiceDescriptor/artifacts";
 
 
     public boolean isValidRappPackage(MultipartFile multipartFile) {
@@ -107,10 +112,18 @@ public class RappCsarConfigurationHandler {
                 "COMPOSITIONID", String.valueOf(compositionId));
     }
 
+    public ByteArrayResource getArtifactPayload(Rapp rapp, String location) {
+        return new ByteArrayResource(getByteArrayStreamPayload(rapp, location).toByteArray());
+    }
+
     String getPayload(Rapp rapp, String location) {
+        return getByteArrayStreamPayload(rapp, location).toString();
+    }
+
+    ByteArrayOutputStream getByteArrayStreamPayload(Rapp rapp, String location) {
         logger.debug("Getting payload for {} from {}", rapp.getRappId(), location);
         File csarFile = getCsarFile(rapp);
-        return getFileFromCsar(csarFile, location).toString();
+        return getFileFromCsar(csarFile, location);
     }
 
     File getCsarFile(Rapp rapp) {
@@ -163,11 +176,8 @@ public class RappCsarConfigurationHandler {
         if (asdLocation != null && !asdLocation.isEmpty() && isFileExistsInCsar(multipartFile, asdLocation)) {
             try {
                 String asdContent = getFileFromCsar(multipartFile, asdLocation).toString();
-                String asdJsonContent = new Gson().toJsonTree(new Yaml().load(asdContent)).toString();
-                JsonNode jsonNode = objectMapper.readTree(asdJsonContent);
-                List<String> artifactFileList =
-                        jsonNode.at("/topology_template/node_templates/applicationServiceDescriptor/artifacts")
-                                .findValuesAsText("file");
+                JsonNode jsonNode = getAsdContentNode(asdContent);
+                List<String> artifactFileList = jsonNode.at(ARTIFACTS_LOCATION_JSON_POINTER).findValuesAsText("file");
                 return artifactFileList.stream()
                                .allMatch(artifactFile -> isFileExistsInCsar(multipartFile, artifactFile));
             } catch (RappHandlerException e) {
@@ -180,21 +190,51 @@ public class RappCsarConfigurationHandler {
         throw new RappHandlerException(HttpStatus.BAD_REQUEST, "ASD definition in rApp package is invalid.");
     }
 
+    JsonNode getAsdContentNode(String asdContent) throws JsonProcessingException {
+        return objectMapper.readTree(new Gson().toJsonTree(new Yaml().load(asdContent)).toString());
+    }
+
+    String getAsdDefinitionLocation(final File csarFile) {
+        return getAsdDefinitionLocation(getFileFromCsar(csarFile, TOSCA_METADATA_LOCATION).toString());
+    }
+
     String getAsdDefinitionLocation(final MultipartFile multipartFile) {
+        return getAsdDefinitionLocation(getFileFromCsar(multipartFile, TOSCA_METADATA_LOCATION).toString());
+    }
+
+    String getAsdDefinitionLocation(final String toscaMetadata) {
         String asdLocation = "";
-        final ByteArrayOutputStream fileContent = getFileFromCsar(multipartFile, TOSCA_METADATA_LOCATION);
-        if (fileContent != null) {
-            final String toscaMetadata = fileContent.toString();
-            if (!toscaMetadata.isEmpty()) {
-                final String entry =
-                        filter(on("\n").split(toscaMetadata), line -> line.contains(ENTRY_DEFINITIONS_INDEX)).iterator()
-                                .next();
-                asdLocation = entry.replace(ENTRY_DEFINITIONS_INDEX + ":", "").trim();
-            }
+        if (toscaMetadata != null && !toscaMetadata.isEmpty()) {
+            final String entry =
+                    filter(on("\n").split(toscaMetadata), line -> line.contains(ENTRY_DEFINITIONS_INDEX)).iterator()
+                            .next();
+            asdLocation = entry.replace(ENTRY_DEFINITIONS_INDEX + ":", "").trim();
         }
         return asdLocation;
     }
 
+    public List<DeploymentItem> getDeploymentItems(Rapp rApp) {
+        List<DeploymentItem> deploymentItems = new ArrayList<>();
+        File csarFile = getCsarFile(rApp);
+        String asdDefinitionLocation = getAsdDefinitionLocation(csarFile);
+        if (asdDefinitionLocation != null && !asdDefinitionLocation.isEmpty()) {
+            try {
+                String asdContent = getFileFromCsar(csarFile, asdDefinitionLocation).toString();
+                JsonNode jsonNode = getAsdContentNode(asdContent);
+                JsonNode artifactsJsonNode = jsonNode.at(ARTIFACTS_LOCATION_JSON_POINTER);
+                artifactsJsonNode.forEach(artifactJsonNode -> {
+                    DeploymentItem deploymentItem =
+                            objectMapper.convertValue(artifactJsonNode.at("/properties"), DeploymentItem.class);
+                    deploymentItem.setFile(artifactJsonNode.at("/file").asText());
+                    deploymentItems.add(deploymentItem);
+                });
+            } catch (Exception e) {
+                logger.warn("Unable to get the deployment items", e);
+            }
+        }
+        return deploymentItems;
+    }
+
 
     public String getSmeProviderDomainPayload(Rapp rapp, RappSMEInstance rappSMEInstance) {
         return getPayload(rapp, getResourceUri(SME_PROVIDER_FUNCS_LOCATION, rappSMEInstance.getProviderFunction()));
index 81468ee..e769b12 100755 (executable)
@@ -27,10 +27,13 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.gson.JsonSyntaxException;
 import com.oransc.rappmanager.models.exception.RappHandlerException;
 import com.oransc.rappmanager.models.rapp.Rapp;
 import com.oransc.rappmanager.models.rapp.RappResources;
@@ -41,6 +44,7 @@ import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.util.List;
 import java.util.Set;
 import java.util.UUID;
 import java.util.stream.Stream;
@@ -51,6 +55,7 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.SpyBean;
 import org.springframework.http.HttpStatus;
@@ -67,11 +72,14 @@ class RappCsarConfigurationHandlerTest {
 
     String validCsarFileLocation = "src/test/resources/";
 
-
     private final String validRappFile = "valid-rapp-package.csar";
 
     private final String invalidRappFile = "invalid-rapp-package.csar";
 
+    private final String invalidRappNoAsdFile = "invalid-rapp-package-no-asd-yaml.csar";
+
+    private final String invalidRappEmptyAsdFile = "invalid-rapp-package-empty-asd-yaml.csar";
+
     @Test
     void testCsarPackageValidationSuccess() throws IOException {
         String rappCsarPath = validCsarFileLocation + File.separator + validRappFile;
@@ -84,7 +92,6 @@ class RappCsarConfigurationHandlerTest {
     @ParameterizedTest
     @MethodSource("getInvalidCsarPackage")
     void testCsarPackageValidationFailure(MultipartFile multipartFile) {
-        System.out.println(multipartFile.getOriginalFilename());
         RappHandlerException exception = assertThrows(RappHandlerException.class,
                 () -> rappCsarConfigurationHandler.isValidRappPackage(multipartFile));
         assertEquals(HttpStatus.BAD_REQUEST, exception.getStatusCode());
@@ -361,4 +368,32 @@ class RappCsarConfigurationHandlerTest {
         assertNotNull(dmeInfoConsumerPayload);
     }
 
+    @Test
+    void testListDeploymentItems() {
+        Rapp rapp = Rapp.builder().name("").packageName(validRappFile).packageLocation(validCsarFileLocation).build();
+        List<DeploymentItem> deploymentItems = rappCsarConfigurationHandler.getDeploymentItems(rapp);
+        assertEquals(2, deploymentItems.size());
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = {invalidRappNoAsdFile, invalidRappEmptyAsdFile})
+    void testListDeploymentItemsNoAsd(String packageName) {
+        Rapp rapp = Rapp.builder().name("").packageName(packageName).packageLocation(validCsarFileLocation).build();
+        assertThat(rappCsarConfigurationHandler.getDeploymentItems(rapp)).isEmpty();
+    }
+
+    @Test
+    void testListDeploymentItemsWithException() throws JsonProcessingException {
+        Rapp rapp = Rapp.builder().name("").packageName(validRappFile).packageLocation(validCsarFileLocation).build();
+        doThrow(new JsonSyntaxException("")).when(rappCsarConfigurationHandler).getAsdContentNode(any());
+        assertThat(rappCsarConfigurationHandler.getDeploymentItems(rapp)).isEmpty();
+    }
+
+    @Test
+    void testGetArtifactPayload() {
+        Rapp rapp = Rapp.builder().name("").packageName(validRappFile).packageLocation(validCsarFileLocation).build();
+        assertNotNull(rappCsarConfigurationHandler.getArtifactPayload(rapp,
+                "Artifacts/Deployment/HELM/ransliceassurance-1.0.0.tgz"));
+
+    }
 }
diff --git a/rapp-manager-models/src/test/resources/invalid-rapp-package-empty-asd-yaml.csar b/rapp-manager-models/src/test/resources/invalid-rapp-package-empty-asd-yaml.csar
new file mode 100755 (executable)
index 0000000..e614a19
Binary files /dev/null and b/rapp-manager-models/src/test/resources/invalid-rapp-package-empty-asd-yaml.csar differ
index ffe2f4b..667317e 100755 (executable)
Binary files a/rapp-manager-models/src/test/resources/valid-rapp-package.csar and b/rapp-manager-models/src/test/resources/valid-rapp-package.csar differ
index e369010..96543b8 100755 (executable)
@@ -7,7 +7,7 @@ imports:
 \r
 topology_template:\r
   node_templates:\r
-   applicationServiceDescriptor:\r
+    applicationServiceDescriptor:\r
       type: tosca.nodes.asd\r
       description: "rapp-all"\r
       properties:\r
@@ -25,10 +25,14 @@ topology_template:
           file: "Artifacts/Deployment/HELM/ransliceassurance-1.0.0.tgz"\r
           properties:\r
             artifact_type: "helm_chart"\r
-            itemId: 1\r
+            target_server: "chartmuseum"\r
+            target_server_uri: "http://10.101.0.106:8879/charts/api/charts"\r
+            item_id: 1\r
         oru-app:\r
           type: tosca.artifacts.asd.deploymentItem\r
           file: "Artifacts/Deployment/HELM/orufhrecovery-1.0.0.tgz"\r
           properties:\r
             artifact_type: "helm_chart"\r
-            itemId: 2\r
+            target_server: "chartmuseum"\r
+            target_server_uri: "http://10.101.0.106:8879/charts/api/charts"\r
+            item_id: 2
\ No newline at end of file
index e2a788a..7ac8c10 100755 (executable)
@@ -43,19 +43,30 @@ node_types:
 
 artifact_types:
 tosca.artifacts.asd.deploymentItem:
-    version: 0.1
-    derived_from: tosca.artifacts.Root
-    description: "Describes the artifact type of asd deployment item"
-    file: "Relative path of the artifact in the package"
-    properties:
-        item_id:
-            description: "The identifier of this asd deployment item"
-            required: true
-            type: string
-        artifact_type:
-            description: >
-                Specify artifact type.
-            required: true
-            type: string
-            constraints:
-                - valid_values: ["helm_chart"]
+  version: 0.1
+  derived_from: tosca.artifacts.Root
+  description: "Describes the artifact type of asd deployment item"
+  file: "Relative path of the artifact in the package"
+  properties:
+    item_id:
+      description: "The identifier of this asd deployment item"
+      required: true
+      type: string
+    artifact_type:
+      description: >
+        Specify artifact type.
+      required: true
+      type: string
+      constraints:
+        - valid_values: ["helm_chart"]
+    target_server:
+      description: >
+        Specify target server for artifact.
+      required: true
+      type: string
+      constraints:
+        - valid_values: [ "chartmuseum" ]
+    target_server_uri:
+      description: "URI of the target server"
+      required: true
+      type: string
\ No newline at end of file