From dc1302f9cc01b098fc1a44bcdd115350c032ee45 Mon Sep 17 00:00:00 2001 From: "aravind.est" Date: Tue, 3 Oct 2023 15:32:17 +0100 Subject: [PATCH] Add status check between ACM depriming and deletion 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 Change-Id: I44a47145141f50089a92a34276c754ab3e6ab5e6 --- pom.xml | 4 +- rapp-manager-acm/pom.xml | 7 +- .../JacksonMessageConverterConfiguration.java | 37 ++++++ .../rappmanager/acm/service/AcmDeployer.java | 35 ++++- .../rappmanager/acm/service/AcmDeployerTest.java | 141 ++++++++++++++++++++- .../acm/service/BeanTestConfiguration.java | 7 +- 6 files changed, 218 insertions(+), 13 deletions(-) create mode 100755 rapp-manager-acm/src/main/java/com/oransc/rappmanager/acm/configuration/JacksonMessageConverterConfiguration.java diff --git a/pom.xml b/pom.xml index 0a8f1d1..6981e08 100755 --- a/pom.xml +++ b/pom.xml @@ -48,10 +48,10 @@ 17 - 6.6.0 + 7.0.1 0.30.0 0.8.10 - 6.4.2 + 6.4.3 0.2.6 3.2.1 6.0.8 diff --git a/rapp-manager-acm/pom.xml b/rapp-manager-acm/pom.xml index 0e01bb4..9cbf7ab 100755 --- a/rapp-manager-acm/pom.xml +++ b/rapp-manager-acm/pom.xml @@ -80,6 +80,11 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-web + test + @@ -110,7 +115,7 @@ 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, 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 index 0000000..970c70b --- /dev/null +++ b/rapp-manager-acm/src/main/java/com/oransc/rappmanager/acm/configuration/JacksonMessageConverterConfiguration.java @@ -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))); + } +} diff --git a/rapp-manager-acm/src/main/java/com/oransc/rappmanager/acm/service/AcmDeployer.java b/rapp-manager-acm/src/main/java/com/oransc/rappmanager/acm/service/AcmDeployer.java index 0cfeb9b..26d4418 100755 --- a/rapp-manager-acm/src/main/java/com/oransc/rappmanager/acm/service/AcmDeployer.java +++ b/rapp-manager-acm/src/main/java/com/oransc/rappmanager/acm/service/AcmDeployer.java @@ -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 { diff --git a/rapp-manager-acm/src/test/java/com/oransc/rappmanager/acm/service/AcmDeployerTest.java b/rapp-manager-acm/src/test/java/com/oransc/rappmanager/acm/service/AcmDeployerTest.java index b866b5a..5268640 100755 --- a/rapp-manager-acm/src/test/java/com/oransc/rappmanager/acm/service/AcmDeployerTest.java +++ b/rapp-manager-acm/src/test/java/com/oransc/rappmanager/acm/service/AcmDeployerTest.java @@ -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 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; + } } diff --git a/rapp-manager-acm/src/test/java/com/oransc/rappmanager/acm/service/BeanTestConfiguration.java b/rapp-manager-acm/src/test/java/com/oransc/rappmanager/acm/service/BeanTestConfiguration.java index f59f8fb..a2c2f67 100755 --- a/rapp-manager-acm/src/test/java/com/oransc/rappmanager/acm/service/BeanTestConfiguration.java +++ b/rapp-manager-acm/src/test/java/com/oransc/rappmanager/acm/service/BeanTestConfiguration.java @@ -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") -- 2.16.6