Add status check between ACM depriming and deletion 48/11848/1
authoraravind.est <aravindhan.a@est.tech>
Tue, 3 Oct 2023 14:32:17 +0000 (15:32 +0100)
committeraravind.est <aravindhan.a@est.tech>
Tue, 3 Oct 2023 14:32:17 +0000 (15:32 +0100)
Openapi maven version upgraded.
ACM model library version upgraded.
Status check added between depriming and deletion.
Code coverage improvement in ACM.

Issue-ID: NONRTRIC-929
Signed-off-by: aravind.est <aravindhan.a@est.tech>
Change-Id: I44a47145141f50089a92a34276c754ab3e6ab5e6

pom.xml
rapp-manager-acm/pom.xml
rapp-manager-acm/src/main/java/com/oransc/rappmanager/acm/configuration/JacksonMessageConverterConfiguration.java [new file with mode: 0755]
rapp-manager-acm/src/main/java/com/oransc/rappmanager/acm/service/AcmDeployer.java
rapp-manager-acm/src/test/java/com/oransc/rappmanager/acm/service/AcmDeployerTest.java
rapp-manager-acm/src/test/java/com/oransc/rappmanager/acm/service/BeanTestConfiguration.java

diff --git a/pom.xml b/pom.xml
index 0a8f1d1..6981e08 100755 (executable)
--- a/pom.xml
+++ b/pom.xml
     </repositories>
     <properties>
         <java.version>17</java.version>
-        <openapi.maven.version>6.6.0</openapi.maven.version>
+        <openapi.maven.version>7.0.1</openapi.maven.version>
         <docker-maven-plugin>0.30.0</docker-maven-plugin>
         <jacoco-maven-plugin.version>0.8.10</jacoco-maven-plugin.version>
-        <onap.acm.models.version>6.4.2</onap.acm.models.version>
+        <onap.acm.models.version>6.4.3</onap.acm.models.version>
         <openapi.jackson.databind.nullable.version>0.2.6</openapi.jackson.databind.nullable.version>
         <spring.statemachine.version>3.2.1</spring.statemachine.version>
         <spring.context.version>6.0.8</spring.context.version>
index 0e01bb4..9cbf7ab 100755 (executable)
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <scope>test</scope>
+        </dependency>
 
     </dependencies>
 
                                 ToscaNodeTemplate=org.onap.policy.models.tosca.authorative.concepts.ToscaNodeTemplate,
                                 AutomationCompositions=org.onap.policy.clamp.models.acm.concepts.AutomationCompositions,
                                 AutomationComposition=org.onap.policy.clamp.models.acm.concepts.AutomationComposition,
-                                <!--                                AutomationCompositionDefinition=org.onap.policy.clamp.models.acm.concepts.AutomationCompositionDefinition,-->
+                                AutomationCompositionDefinition=org.onap.policy.clamp.models.acm.concepts.AutomationCompositionDefinition,
                                 SimpleResponse=org.onap.policy.clamp.models.acm.messages.rest.SimpleResponse,
                                 AcTypeStateUpdate=org.onap.policy.clamp.models.acm.messages.rest.commissioning.AcTypeStateUpdate,
                                 AcInstanceStateUpdate=org.onap.policy.clamp.models.acm.messages.rest.instantiation.AcInstanceStateUpdate,
diff --git a/rapp-manager-acm/src/main/java/com/oransc/rappmanager/acm/configuration/JacksonMessageConverterConfiguration.java b/rapp-manager-acm/src/main/java/com/oransc/rappmanager/acm/configuration/JacksonMessageConverterConfiguration.java
new file mode 100755 (executable)
index 0000000..970c70b
--- /dev/null
@@ -0,0 +1,37 @@
+/*-
+ * ============LICENSE_START======================================================================
+ * Copyright (C) 2023 Nordix Foundation. 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.acm.configuration;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaEntityKey;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+
+@Configuration
+public class JacksonMessageConverterConfiguration extends MappingJackson2HttpMessageConverter {
+
+    public JacksonMessageConverterConfiguration(ObjectMapper objectMapper) {
+        super(objectMapper);
+        //This is to fix the AutomationCompositionDefinition deserialization issue via Openapi generator
+        objectMapper.registerModule(
+                new SimpleModule().addKeyDeserializer(ToscaEntityKey.class, StdKeyDeserializer.forType(String.class)));
+    }
+}
index 0cfeb9b..26d4418 100755 (executable)
@@ -31,6 +31,7 @@ import com.oransc.rappmanager.models.statemachine.RappInstanceStateMachine;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 import lombok.RequiredArgsConstructor;
+import org.onap.policy.clamp.models.acm.concepts.AcTypeState;
 import org.onap.policy.clamp.models.acm.concepts.AutomationComposition;
 import org.onap.policy.clamp.models.acm.concepts.DeployState;
 import org.onap.policy.clamp.models.acm.concepts.LockState;
@@ -190,9 +191,12 @@ public class AcmDeployer implements RappDeployer {
     public boolean deprimeRapp(Rapp rapp) {
         try {
             primeACMComposition(rapp.getCompositionId(), PrimeOrder.DEPRIME);
-            CommissioningResponse commissioningResponse = deleteComposition(rapp.getCompositionId());
-            if (commissioningResponse != null) {
-                return true;
+            if (waitForCompositionTargetState(rapp.getCompositionId(), AcTypeState.COMMISSIONED)) {
+                CommissioningResponse commissioningResponse = deleteComposition(rapp.getCompositionId());
+                if (commissioningResponse != null) {
+                    rapp.setCompositionId(null);
+                    return true;
+                }
             }
         } catch (Exception e) {
             logger.warn("Failed deprime automation composition", e);
@@ -201,6 +205,31 @@ public class AcmDeployer implements RappDeployer {
         return false;
     }
 
+    boolean waitForCompositionTargetState(UUID compositionId, AcTypeState acTypeState) {
+        boolean targetCompositionStateTransition = false;
+        try {
+            for (int i = 0; i < acmConfiguration.getMaxRetries(); i++) {
+                logger.debug("Composition state check {}", i + 1);
+                if (isCompositionStateEquals(compositionId, acTypeState)) {
+                    logger.debug("Composition {} state is {}", compositionId, acTypeState);
+                    targetCompositionStateTransition = true;
+                    break;
+                } else {
+                    TimeUnit.SECONDS.sleep(acmConfiguration.getRetryInterval());
+                }
+            }
+        } catch (Exception e) {
+            logger.warn("Unable to get composition state for composition {}", compositionId, e);
+            Thread.currentThread().interrupt();
+        }
+        return targetCompositionStateTransition;
+    }
+
+    boolean isCompositionStateEquals(UUID compositionId, AcTypeState acTypeState) {
+        return automationCompositionDefinitionApiClient.getCompositionDefinition(compositionId, UUID.randomUUID())
+                       .getState().equals(acTypeState);
+    }
+
     public void syncRappInstanceStatus(UUID compositionId, RappInstance rappInstance) {
         if (rappInstance.getAcm().getAcmInstanceId() != null) {
             try {
index b866b5a..5268640 100755 (executable)
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 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.withServerError;
 import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
@@ -45,16 +46,23 @@ import com.oransc.rappmanager.models.statemachine.RappInstanceStateMachine;
 import com.oransc.rappmanager.models.statemachine.RappInstanceStateMachineConfig;
 import java.io.IOException;
 import java.util.UUID;
+import java.util.stream.Stream;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.onap.policy.clamp.models.acm.concepts.AcTypeState;
 import org.onap.policy.clamp.models.acm.concepts.AutomationComposition;
+import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionDefinition;
 import org.onap.policy.clamp.models.acm.concepts.DeployState;
 import org.onap.policy.clamp.models.acm.concepts.LockState;
 import org.onap.policy.clamp.models.acm.messages.rest.commissioning.CommissioningResponse;
 import org.onap.policy.clamp.models.acm.messages.rest.commissioning.PrimeOrder;
 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.InstantiationResponse;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 import org.springframework.boot.test.context.SpringBootTest;
@@ -84,11 +92,12 @@ class AcmDeployerTest {
     RappInstanceStateMachine rappInstanceStateMachine;
     @Autowired
     RappCsarConfigurationHandler rappCsarConfigurationHandler;
+    @Autowired
+    ObjectMapper objectMapper;
 
     RappResourceBuilder rappResourceBuilder = new RappResourceBuilder();
     private final String validRappFile = "valid-rapp-package.csar";
     String validCsarFileLocation = "src/test/resources/";
-    ObjectMapper objectMapper = new ObjectMapper();
     String URI_ACM_COMPOSITIONS, URI_ACM_COMPOSITION, URI_ACM_INSTANCES, URI_ACM_INSTANCE;
 
     @BeforeAll
@@ -258,17 +267,47 @@ class AcmDeployerTest {
     }
 
     @Test
-    void testSyncRappInstanceStatus() throws JsonProcessingException {
+    void testUndeployRappInstanceACMErrorFailure() throws JsonProcessingException {
         UUID compositionId = UUID.randomUUID();
+        UUID rappId = UUID.randomUUID();
         UUID instanceId = UUID.randomUUID();
-        expectAcmGetInstanceToReturnState(compositionId, instanceId, DeployState.UNDEPLOYING, LockState.UNLOCKING,
+        Rapp rapp = Rapp.builder().name(rappId.toString()).packageName(validRappFile).compositionId(compositionId)
+                            .state(RappState.PRIMED).build();
+        expectAcmGetInstanceToReturnState(compositionId, instanceId, DeployState.DEPLOYED, LockState.LOCKED,
                 ExpectedCount.once());
+        mockServer.expect(ExpectedCount.once(), requestTo(String.format(URI_ACM_INSTANCE, compositionId, instanceId)))
+                .andExpect(method(HttpMethod.PUT)).andRespond(withStatus(HttpStatus.ACCEPTED));
+        mockServer.expect(ExpectedCount.manyTimes(),
+                        requestTo(String.format(URI_ACM_INSTANCE, compositionId, instanceId))).andExpect(method(HttpMethod.GET))
+                .andRespond(withServerError());
+        RappInstance rappInstance = rappResourceBuilder.getRappInstance();
+        rappInstance.getAcm().setAcmInstanceId(instanceId);
+        rappInstanceStateMachine.onboardRappInstance(rappInstance.getRappInstanceId());
+        boolean rappUndeployStateActual = acmDeployer.undeployRappInstance(rapp, rappInstance);
+        mockServer.verify();
+        assertFalse(rappUndeployStateActual);
+    }
+
+    @ParameterizedTest
+    @MethodSource("getAcmStatusEventMap")
+    void testSyncRappInstanceStatus(DeployState deployState, LockState lockState, RappEvent rappEvent)
+            throws JsonProcessingException {
+        UUID compositionId = UUID.randomUUID();
+        UUID instanceId = UUID.randomUUID();
+        expectAcmGetInstanceToReturnState(compositionId, instanceId, deployState, lockState, ExpectedCount.once());
         RappInstance rappInstance = rappResourceBuilder.getRappInstance();
         rappInstance.getAcm().setAcmInstanceId(instanceId);
         rappInstanceStateMachine.onboardRappInstance(rappInstance.getRappInstanceId());
         acmDeployer.syncRappInstanceStatus(compositionId, rappInstance);
         mockServer.verify();
-        verify(rappInstanceStateMachine, times(1)).sendRappInstanceEvent(rappInstance, RappEvent.UNDEPLOYING);
+        verify(rappInstanceStateMachine, times(1)).sendRappInstanceEvent(rappInstance, rappEvent);
+    }
+
+    private static Stream<Arguments> getAcmStatusEventMap() {
+        return Stream.of(Arguments.of(DeployState.UNDEPLOYING, LockState.UNLOCKING, RappEvent.UNDEPLOYING),
+                Arguments.of(DeployState.DEPLOYED, LockState.LOCKED, RappEvent.ACMDEPLOYED),
+                Arguments.of(DeployState.DEPLOYING, LockState.LOCKING, RappEvent.DEPLOYING),
+                Arguments.of(DeployState.UNDEPLOYED, LockState.UNLOCKED, RappEvent.ACMUNDEPLOYED));
     }
 
     @Test
@@ -357,6 +396,45 @@ class AcmDeployerTest {
         commissioningResponseExpected.setCompositionId(compositionId);
         mockServer.expect(ExpectedCount.once(), requestTo(String.format(URI_ACM_COMPOSITION, compositionId)))
                 .andExpect(method(HttpMethod.PUT)).andRespond(withStatus(HttpStatus.OK));
+        AutomationCompositionDefinition automationCompositionDefinition =
+                getAutomationCompositionDefinition(compositionId, AcTypeState.COMMISSIONED);
+        mockServer.expect(ExpectedCount.once(), requestTo(String.format(URI_ACM_COMPOSITION, compositionId)))
+                .andExpect(method(HttpMethod.GET)).andRespond(
+                        withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)
+                                .body(objectMapper.writeValueAsString(automationCompositionDefinition)));
+        mockServer.expect(ExpectedCount.once(), requestTo(String.format(URI_ACM_COMPOSITION, compositionId)))
+                .andExpect(method(HttpMethod.DELETE)).andRespond(
+                        withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)
+                                .body(objectMapper.writeValueAsString(commissioningResponseExpected)));
+
+        boolean deprimeRapp = acmDeployer.deprimeRapp(rapp);
+        mockServer.verify();
+        assertTrue(deprimeRapp);
+    }
+
+    @Test
+    void testDeprimeRappClientRetry() throws JsonProcessingException {
+        UUID compositionId = UUID.randomUUID();
+        RappResources rappResources = rappResourceBuilder.getResources();
+        Rapp rapp = Rapp.builder().rappId(UUID.randomUUID()).name("").packageName(validRappFile)
+                            .packageLocation(validCsarFileLocation).state(RappState.COMMISSIONED)
+                            .compositionId(compositionId).rappResources(rappResources).build();
+
+        CommissioningResponse commissioningResponseExpected = new CommissioningResponse();
+        commissioningResponseExpected.setCompositionId(compositionId);
+        mockServer.expect(ExpectedCount.once(), requestTo(String.format(URI_ACM_COMPOSITION, compositionId)))
+                .andExpect(method(HttpMethod.PUT)).andRespond(withStatus(HttpStatus.OK));
+        AutomationCompositionDefinition automationCompositionDefinition =
+                getAutomationCompositionDefinition(compositionId, AcTypeState.DEPRIMING);
+        mockServer.expect(ExpectedCount.once(), requestTo(String.format(URI_ACM_COMPOSITION, compositionId)))
+                .andExpect(method(HttpMethod.GET)).andRespond(
+                        withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)
+                                .body(objectMapper.writeValueAsString(automationCompositionDefinition)));
+        automationCompositionDefinition = getAutomationCompositionDefinition(compositionId, AcTypeState.COMMISSIONED);
+        mockServer.expect(ExpectedCount.once(), requestTo(String.format(URI_ACM_COMPOSITION, compositionId)))
+                .andExpect(method(HttpMethod.GET)).andRespond(
+                        withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)
+                                .body(objectMapper.writeValueAsString(automationCompositionDefinition)));
         mockServer.expect(ExpectedCount.once(), requestTo(String.format(URI_ACM_COMPOSITION, compositionId)))
                 .andExpect(method(HttpMethod.DELETE)).andRespond(
                         withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)
@@ -368,7 +446,7 @@ class AcmDeployerTest {
     }
 
     @Test
-    void testDeprimeFailureRapp() {
+    void testDeprimeFailureRapp() throws JsonProcessingException {
         UUID compositionId = UUID.randomUUID();
         RappResources rappResources = rappResourceBuilder.getResources();
         Rapp rapp = Rapp.builder().rappId(UUID.randomUUID()).name("").packageName(validRappFile)
@@ -377,6 +455,12 @@ class AcmDeployerTest {
 
         mockServer.expect(ExpectedCount.once(), requestTo(String.format(URI_ACM_COMPOSITION, compositionId)))
                 .andExpect(method(HttpMethod.PUT)).andRespond(withStatus(HttpStatus.OK));
+        AutomationCompositionDefinition automationCompositionDefinition =
+                getAutomationCompositionDefinition(compositionId, AcTypeState.COMMISSIONED);
+        mockServer.expect(ExpectedCount.once(), requestTo(String.format(URI_ACM_COMPOSITION, compositionId)))
+                .andExpect(method(HttpMethod.GET)).andRespond(
+                        withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)
+                                .body(objectMapper.writeValueAsString(automationCompositionDefinition)));
         mockServer.expect(ExpectedCount.once(), requestTo(String.format(URI_ACM_COMPOSITION, compositionId)))
                 .andExpect(method(HttpMethod.DELETE)).andRespond(withStatus(HttpStatus.INTERNAL_SERVER_ERROR));
 
@@ -385,6 +469,45 @@ class AcmDeployerTest {
         assertFalse(deprimeRapp);
     }
 
+    @Test
+    void testDeprimeACMStatusFailureRapp() throws JsonProcessingException {
+        UUID compositionId = UUID.randomUUID();
+        RappResources rappResources = rappResourceBuilder.getResources();
+        Rapp rapp = Rapp.builder().rappId(UUID.randomUUID()).name("").packageName(validRappFile)
+                            .packageLocation(validCsarFileLocation).state(RappState.COMMISSIONED)
+                            .compositionId(compositionId).rappResources(rappResources).build();
+
+        mockServer.expect(ExpectedCount.once(), requestTo(String.format(URI_ACM_COMPOSITION, compositionId)))
+                .andExpect(method(HttpMethod.PUT)).andRespond(withStatus(HttpStatus.OK));
+        AutomationCompositionDefinition automationCompositionDefinition =
+                getAutomationCompositionDefinition(compositionId, AcTypeState.DEPRIMING);
+        mockServer.expect(ExpectedCount.manyTimes(), requestTo(String.format(URI_ACM_COMPOSITION, compositionId)))
+                .andExpect(method(HttpMethod.GET)).andRespond(
+                        withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)
+                                .body(objectMapper.writeValueAsString(automationCompositionDefinition)));
+
+        boolean deprimeRapp = acmDeployer.deprimeRapp(rapp);
+        mockServer.verify();
+        assertFalse(deprimeRapp);
+    }
+
+    @Test
+    void testDeprimeACMStatusErrorRapp() {
+        UUID compositionId = UUID.randomUUID();
+        RappResources rappResources = rappResourceBuilder.getResources();
+        Rapp rapp = Rapp.builder().rappId(UUID.randomUUID()).name("").packageName(validRappFile)
+                            .packageLocation(validCsarFileLocation).state(RappState.COMMISSIONED)
+                            .compositionId(compositionId).rappResources(rappResources).build();
+
+        mockServer.expect(ExpectedCount.once(), requestTo(String.format(URI_ACM_COMPOSITION, compositionId)))
+                .andExpect(method(HttpMethod.PUT)).andRespond(withStatus(HttpStatus.OK));
+        mockServer.expect(ExpectedCount.manyTimes(), requestTo(String.format(URI_ACM_COMPOSITION, compositionId)))
+                .andExpect(method(HttpMethod.GET)).andRespond(withServerError());
+        boolean deprimeRapp = acmDeployer.deprimeRapp(rapp);
+        mockServer.verify();
+        assertFalse(deprimeRapp);
+    }
+
     @Test
     void testDeprimeExceptionRapp() {
         Rapp rapp = Rapp.builder().rappId(UUID.randomUUID()).name("").packageName(validRappFile)
@@ -432,4 +555,12 @@ class AcmDeployerTest {
                         withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)
                                 .body(objectMapper.writeValueAsString(automationCompositionDeployed)));
     }
+
+    AutomationCompositionDefinition getAutomationCompositionDefinition(UUID compositionId, AcTypeState acTypeState) {
+        AutomationCompositionDefinition automationCompositionDefinition = new AutomationCompositionDefinition();
+        automationCompositionDefinition.setCompositionId(compositionId);
+        automationCompositionDefinition.setState(acTypeState);
+        automationCompositionDefinition.setServiceTemplate(new ToscaServiceTemplate());
+        return automationCompositionDefinition;
+    }
 }
index f59f8fb..a2c2f67 100755 (executable)
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.oransc.rappmanager.acm.ApiClient;
 import com.oransc.rappmanager.acm.configuration.ACMConfiguration;
+import com.oransc.rappmanager.acm.configuration.JacksonMessageConverterConfiguration;
 import com.oransc.rappmanager.acm.rest.AutomationCompositionDefinitionApiClient;
 import com.oransc.rappmanager.acm.rest.AutomationCompositionInstanceApiClient;
 import com.oransc.rappmanager.acm.rest.ParticipantMonitoringApiClient;
@@ -40,8 +41,10 @@ public class BeanTestConfiguration {
     }
 
     @Bean
-    public RestTemplate restTemplate(RestTemplateBuilder builder) {
-        return builder.build();
+    public RestTemplate restTemplate(RestTemplateBuilder builder, ObjectMapper objectMapper) {
+        RestTemplate restTemplate = builder.build();
+        restTemplate.getMessageConverters().add(new JacksonMessageConverterConfiguration(objectMapper));
+        return restTemplate;
     }
 
     @Bean("acmApiClient")