Fix Duplicate key value violation error in ACM-R 69/142869/6
authorFrancescoFioraEst <francesco.fiora@est.tech>
Tue, 6 Jan 2026 13:17:37 +0000 (13:17 +0000)
committerFrancesco Fiora <francesco.fiora@est.tech>
Fri, 16 Jan 2026 14:19:47 +0000 (14:19 +0000)
Issue-ID: POLICY-5530
Change-Id: I9486dfc04b528bca9b3afce233edfc31a32c26b8
Signed-off-by: FrancescoFioraEst <francesco.fiora@est.tech>
models/src/main/java/org/onap/policy/clamp/models/acm/persistence/provider/MessageProvider.java
models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/MessageProviderTest.java
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionScanner.java
runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/liquibase/HibernateValidationTest.java
runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionScannerTest.java

index 9354ce2..5f9c29c 100644 (file)
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved.
+ * Copyright (C) 2025-2026 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.
@@ -233,14 +233,8 @@ public class MessageProvider {
             return Optional.empty();
         }
         var job = new JpaMessageJob(identificationId.toString());
-        try {
-            var result = messageJobRepository.save(job);
-            return Optional.of(result.getJobId());
-        } catch (RuntimeException ex) {
-            // already exist a job with this identificationId
-            LOGGER.warn(ex.getMessage());
-        }
-        return Optional.empty();
+        var result = messageJobRepository.save(job);
+        return Optional.of(result.getJobId());
     }
 
     /**
index fe8c728..51c06d4 100644 (file)
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved.
+ * Copyright (C) 2025-2026 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.
@@ -34,7 +34,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.UUID;
-import org.hibernate.exception.ConstraintViolationException;
 import org.junit.jupiter.api.Test;
 import org.onap.policy.clamp.models.acm.concepts.AcElementDeployAck;
 import org.onap.policy.clamp.models.acm.concepts.AcTypeState;
@@ -235,16 +234,6 @@ class MessageProviderTest {
         assertThat(opt).isEmpty();
     }
 
-    @Test
-    void testCreateJobFail() {
-        var messageJobRepository = mock(MessageJobRepository.class);
-        var identificationId = UUID.randomUUID();
-        when(messageJobRepository.save(any())).thenThrow(new ConstraintViolationException("", null, ""));
-        var messageProvider = new MessageProvider(mock(MessageRepository.class), messageJobRepository);
-        var opt = messageProvider.createJob(identificationId);
-        assertThat(opt).isEmpty();
-    }
-
     @Test
     void testRemoveJob() {
         var messageJobRepository = mock(MessageJobRepository.class);
index 355d20e..cd5e28b 100644 (file)
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- * Copyright (C) 2021-2025 Nordix Foundation.
+ * Copyright (C) 2021-2026 OpenInfra Foundation Europe. All rights reserved.
  * ================================================================================
  * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
@@ -24,6 +24,7 @@ package org.onap.policy.clamp.acm.runtime.supervision;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Optional;
 import java.util.UUID;
 import lombok.RequiredArgsConstructor;
 import org.onap.policy.clamp.acm.runtime.supervision.scanner.MonitoringScanner;
@@ -33,6 +34,7 @@ import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositi
 import org.onap.policy.clamp.models.acm.persistence.provider.MessageProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.dao.DataIntegrityViolationException;
 import org.springframework.stereotype.Component;
 
 /**
@@ -72,7 +74,7 @@ public class SupervisionScanner {
     }
 
     private void scanAcDefinition(UUID compositionId) {
-        var optJobId = messageProvider.createJob(compositionId);
+        var optJobId = createJob(compositionId);
         if (optJobId.isEmpty()) {
             return;
         }
@@ -82,11 +84,27 @@ public class SupervisionScanner {
 
     private void scanAutomationComposition(UUID instanceId,
             Map<UUID, AutomationCompositionDefinition> acDefinitionMap) {
-        var optJobId = messageProvider.createJob(instanceId);
+        var optJobId = createJob(instanceId);
         if (optJobId.isEmpty()) {
             return;
         }
         monitoringScanner.scanAutomationComposition(instanceId, acDefinitionMap);
         messageProvider.removeJob(optJobId.get());
     }
+
+    /**
+     * Create new Job related to the identificationId.
+     *
+     * @param identificationId the instanceId or compositionId
+     *
+     * @return the jobId if the job has been created or empty if identificationId is already used
+     */
+    public Optional<String> createJob(UUID identificationId) {
+        try {
+            return messageProvider.createJob(identificationId);
+        } catch (DataIntegrityViolationException ex) {
+            LOGGER.debug("Job with this identificationId {} already exists", identificationId);
+        }
+        return Optional.empty();
+    }
 }
index b72a3b6..b6104c0 100644 (file)
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved.
+ *  Copyright (C) 2025-2026 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.
 
 package org.onap.policy.clamp.acm.runtime.liquibase;
 
-import org.junit.jupiter.api.Assertions;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
 import org.junit.jupiter.api.Test;
+import org.onap.policy.clamp.acm.runtime.supervision.SupervisionScanner;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.test.context.ActiveProfiles;
 import org.springframework.test.context.DynamicPropertyRegistry;
@@ -42,6 +48,11 @@ import org.testcontainers.junit.jupiter.Testcontainers;
 @Testcontainers
 class HibernateValidationTest extends AbstractLiquibaseTestBase {
 
+    @Autowired
+    private SupervisionScanner scanner;
+
+    private static final AtomicInteger parallelCount = new AtomicInteger();
+
     @DynamicPropertySource
     static void overrideProperties(DynamicPropertyRegistry registry) {
         registry.add("spring.datasource.url", postgres::getJdbcUrl);
@@ -50,9 +61,18 @@ class HibernateValidationTest extends AbstractLiquibaseTestBase {
         registry.add("spring.datasource.driver-class-name", postgres::getDriverClassName);
     }
 
-    // Dummy test: Hibernate validation runs during context startup and throws exception on validation failure
     @Test
-    void contextStartsAndHibernateValidationPasses() {
-        Assertions.assertTrue(true);
+    void createJobTest() {
+        var list = List.of(1, 2, 3, 4, 5);
+        var id = UUID.randomUUID();
+        list.stream().parallel().forEach(
+                x -> {
+                    var optJob = scanner.createJob(id);
+                    if (optJob.isPresent()) {
+                        parallelCount.getAndIncrement();
+                    }
+                }
+        );
+        assertEquals(1, parallelCount.get());
     }
 }
index 0051238..6502c98 100644 (file)
@@ -65,6 +65,7 @@ import org.onap.policy.clamp.models.acm.persistence.provider.AcDefinitionProvide
 import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider;
 import org.onap.policy.clamp.models.acm.persistence.provider.MessageProvider;
 import org.onap.policy.clamp.models.acm.utils.TimestampHelper;
+import org.springframework.dao.DataIntegrityViolationException;
 
 class SupervisionScannerTest {
 
@@ -291,6 +292,45 @@ class SupervisionScannerTest {
         verify(phaseScanner, times(0)).scanWithPhase(any(), any(), any());
     }
 
+    @Test
+    void testScannerJobFail() {
+        var automationComposition = new AutomationComposition();
+        automationComposition.setInstanceId(INSTANCE_ID);
+        automationComposition.setCompositionId(COMPOSITION_ID);
+        automationComposition.setDeployState(DeployState.DEPLOYING);
+        Set<UUID> set = new HashSet<>();
+        set.add(automationComposition.getInstanceId());
+        var automationCompositionProvider = mock(AutomationCompositionProvider.class);
+        when(automationCompositionProvider.getAcInstancesInTransition()).thenReturn(set);
+        when(automationCompositionProvider.findAutomationComposition(automationComposition.getInstanceId()))
+                .thenReturn(Optional.of(automationComposition));
+
+        var stageScanner = mock(StageScanner.class);
+        var simpleScanner = mock(SimpleScanner.class);
+        when(simpleScanner.scanMessage(any(), any())).thenReturn(new UpdateSync());
+        var phaseScanner = mock(PhaseScanner.class);
+
+        var messageProvider = mock(MessageProvider.class);
+        when(messageProvider.createJob(automationComposition.getInstanceId()))
+                .thenThrow(new DataIntegrityViolationException("", null));
+        var message = new  DocMessage();
+        when(messageProvider.getAllMessages(INSTANCE_ID)).thenReturn(List.of(message));
+        when(messageProvider.findInstanceMessages()).thenReturn(Set.of(INSTANCE_ID));
+
+        var acDefinitionProvider = createAcDefinitionProvider(AcTypeState.PRIMED);
+        var monitoringScanner = new MonitoringScanner(automationCompositionProvider, acDefinitionProvider,
+                mock(AcDefinitionScanner.class), stageScanner, simpleScanner, phaseScanner, messageProvider);
+        var supervisionScanner = new SupervisionScanner(automationCompositionProvider, acDefinitionProvider,
+                messageProvider, monitoringScanner);
+
+        supervisionScanner.run();
+        verify(stageScanner, times(0)).scanStage(any(), any(), any(), any());
+        verify(simpleScanner, times(0)).simpleScan(any(), any());
+        verify(phaseScanner, times(0)).scanWithPhase(any(), any(), any());
+        verify(messageProvider, times(0)).removeMessage(message.getMessageId());
+        verify(messageProvider, times(0)).removeJob(JOB_ID);
+    }
+
     @Test
     void testScanner() {
         var automationComposition = new AutomationComposition();