Fix NullPointerException for cm handle state metrics 53/140553/2
authorhalil.cakal <halil.cakal@est.tech>
Fri, 21 Mar 2025 11:09:58 +0000 (11:09 +0000)
committerhalil.cakal <halil.cakal@est.tech>
Wed, 26 Mar 2025 14:34:32 +0000 (14:34 +0000)
- ensure the certain beans are created in a specific order:
1-AdminCacheConfig
2-CmHandleStateMonitor
3-CmHandleStateConfig
- extract cm handle state config from MicroMeterRegistry to allow
  control order of bean initialization to prevent NullPointerException
- introduce @DependsOn annotation

Issue-ID: CPS-2677

Change-Id: I8dfec54cc7e603bded6a24e7362437042b222fd3
Signed-off-by: halil.cakal <halil.cakal@est.tech>
cps-application/src/main/java/org/onap/cps/config/MicroMeterConfig.java
cps-application/src/test/groovy/org/onap/cps/config/MicroMeterConfigSpec.groovy
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/config/CmHandleStateGaugeConfig.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/CmHandleStateMonitor.java
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/CmHandleStateGaugeConfigSpec.groovy [new file with mode: 0644]

index 6782669..6bf3f87 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- * Copyright (C) 2023-2025 Nordix Foundation.
+ * Copyright (C) 2023-2025 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.cps.config;
 
-import com.hazelcast.map.IMap;
 import io.github.mweirauch.micrometer.jvm.extras.ProcessMemoryMetrics;
 import io.github.mweirauch.micrometer.jvm.extras.ProcessThreadMetrics;
 import io.micrometer.core.aop.TimedAspect;
-import io.micrometer.core.instrument.Gauge;
 import io.micrometer.core.instrument.MeterRegistry;
 import io.micrometer.core.instrument.binder.MeterBinder;
 import lombok.RequiredArgsConstructor;
@@ -36,10 +34,6 @@ import org.springframework.context.annotation.Configuration;
 @RequiredArgsConstructor
 public class MicroMeterConfig {
 
-    private static final String STATE_TAG = "state";
-    private static final String CM_HANDLE_STATE_GAUGE = "cps_ncmp_inventory_cm_handles_by_state";
-    final IMap<String, Integer> cmHandlesByState;
-
     @Bean
     public TimedAspect timedAspect(final MeterRegistry meterRegistry) {
         return new TimedAspect(meterRegistry);
@@ -57,79 +51,4 @@ public class MicroMeterConfig {
         return new ProcessThreadMetrics();
     }
 
-    /**
-     * Register gauge metric for cm handles with state 'advised'.
-     *
-     * @param meterRegistry meter registry
-     * @return cm handle state gauge
-     */
-    @Bean
-    public Gauge advisedCmHandles(final MeterRegistry meterRegistry) {
-        return Gauge.builder(CM_HANDLE_STATE_GAUGE, cmHandlesByState,
-                        value -> cmHandlesByState.get("advisedCmHandlesCount"))
-                .tag(STATE_TAG, "ADVISED")
-                .description("Current number of cm handles in advised state")
-                .register(meterRegistry);
-    }
-
-    /**
-     * Register gauge metric for cm handles with state 'ready'.
-     *
-     * @param meterRegistry meter registry
-     * @return cm handle state gauge
-     */
-    @Bean
-    public Gauge readyCmHandles(final MeterRegistry meterRegistry) {
-        return Gauge.builder(CM_HANDLE_STATE_GAUGE, cmHandlesByState,
-                        value -> cmHandlesByState.get("readyCmHandlesCount"))
-                .tag(STATE_TAG, "READY")
-                .description("Current number of cm handles in ready state")
-                .register(meterRegistry);
-    }
-
-    /**
-     * Register gauge metric for cm handles with state 'locked'.
-     *
-     * @param meterRegistry meter registry
-     * @return cm handle state gauge
-     */
-    @Bean
-    public Gauge lockedCmHandles(final MeterRegistry meterRegistry) {
-        return Gauge.builder(CM_HANDLE_STATE_GAUGE, cmHandlesByState,
-                value -> cmHandlesByState.get("lockedCmHandlesCount"))
-                .tag(STATE_TAG, "LOCKED")
-                .description("Current number of cm handles in locked state")
-                .register(meterRegistry);
-    }
-
-    /**
-     * Register gauge metric for cm handles with state 'deleting'.
-     *
-     * @param meterRegistry meter registry
-     * @return cm handle state gauge
-     */
-    @Bean
-    public Gauge deletingCmHandles(final MeterRegistry meterRegistry) {
-        return Gauge.builder(CM_HANDLE_STATE_GAUGE, cmHandlesByState,
-                        value -> cmHandlesByState.get("deletingCmHandlesCount"))
-                .tag(STATE_TAG, "DELETING")
-                .description("Current number of cm handles in deleting state")
-                .register(meterRegistry);
-    }
-
-    /**
-     * Register gauge metric for cm handles with state 'deleted'.
-     *
-     * @param meterRegistry meter registry
-     * @return cm handle state gauge
-     */
-    @Bean
-    public Gauge deletedCmHandles(final MeterRegistry meterRegistry) {
-        return Gauge.builder(CM_HANDLE_STATE_GAUGE, cmHandlesByState,
-                        value -> cmHandlesByState.get("deletedCmHandlesCount"))
-                .tag(STATE_TAG, "DELETED")
-                .description("Number of cm handles that have been deleted since the application started")
-                .register(meterRegistry);
-    }
-
 }
index faef32b..29cb65c 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- * Copyright (C) 2023-2025 Nordix Foundation.
+ * Copyright (C) 2023-2025 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.cps.config
 
-import com.hazelcast.map.IMap
 import io.micrometer.core.instrument.simple.SimpleMeterRegistry
 import spock.lang.Specification
 
 class MicroMeterConfigSpec extends Specification {
 
-    def cmHandlesByState = Mock(IMap)
-    def objectUnderTest = new MicroMeterConfig(cmHandlesByState)
+    def objectUnderTest = new MicroMeterConfig()
     def simpleMeterRegistry = new SimpleMeterRegistry()
 
     def 'Creating a timed aspect.'() {
@@ -42,20 +40,4 @@ class MicroMeterConfigSpec extends Specification {
             assert objectUnderTest.processThreadMetrics() != null
     }
 
-    def 'Creating gauges for cm handle states.'() {
-        given: 'cache returns value for each state'
-            cmHandlesByState.get(_) >> 1
-        when: 'gauges for each state are created'
-             objectUnderTest.advisedCmHandles(simpleMeterRegistry)
-             objectUnderTest.readyCmHandles(simpleMeterRegistry)
-             objectUnderTest.lockedCmHandles(simpleMeterRegistry)
-             objectUnderTest.deletingCmHandles(simpleMeterRegistry)
-             objectUnderTest.deletedCmHandles(simpleMeterRegistry)
-        then: 'each state has the correct value when queried'
-            ['ADVISED', 'READY', 'LOCKED', 'DELETING', 'DELETED'].each { state ->
-                def gaugeValue = simpleMeterRegistry.get(objectUnderTest.CM_HANDLE_STATE_GAUGE).tag('state',state).gauge().value()
-                assert gaugeValue == 1
-            }
-    }
-
 }
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/config/CmHandleStateGaugeConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/config/CmHandleStateGaugeConfig.java
new file mode 100644 (file)
index 0000000..f63a1bf
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.config;
+
+import com.hazelcast.map.IMap;
+import io.micrometer.core.instrument.Gauge;
+import io.micrometer.core.instrument.MeterRegistry;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.DependsOn;
+
+@Configuration
+@RequiredArgsConstructor
+@DependsOn("cmHandleStateMonitor")
+public class CmHandleStateGaugeConfig {
+
+    private static final String STATE_TAG = "state";
+    private static final String CM_HANDLE_STATE_GAUGE = "cps_ncmp_inventory_cm_handles_by_state";
+    private final IMap<String, Integer> cmHandlesByState;
+
+    /**
+     * Register gauge metric for cm handles with state 'advised'.
+     *
+     * @param meterRegistry meter registry
+     * @return cm handle state gauge
+     */
+    @Bean
+    public Gauge advisedCmHandles(final MeterRegistry meterRegistry) {
+        return Gauge.builder(CM_HANDLE_STATE_GAUGE, cmHandlesByState,
+                value -> cmHandlesByState.get("advisedCmHandlesCount"))
+            .tag(STATE_TAG, "ADVISED")
+            .description("Current number of cm handles in advised state")
+            .register(meterRegistry);
+    }
+
+    /**
+     * Register gauge metric for cm handles with state 'ready'.
+     *
+     * @param meterRegistry meter registry
+     * @return cm handle state gauge
+     */
+    @Bean
+    public Gauge readyCmHandles(final MeterRegistry meterRegistry) {
+        return Gauge.builder(CM_HANDLE_STATE_GAUGE, cmHandlesByState,
+                value -> cmHandlesByState.get("readyCmHandlesCount"))
+            .tag(STATE_TAG, "READY")
+            .description("Current number of cm handles in ready state")
+            .register(meterRegistry);
+    }
+
+    /**
+     * Register gauge metric for cm handles with state 'locked'.
+     *
+     * @param meterRegistry meter registry
+     * @return cm handle state gauge
+     */
+    @Bean
+    public Gauge lockedCmHandles(final MeterRegistry meterRegistry) {
+        return Gauge.builder(CM_HANDLE_STATE_GAUGE, cmHandlesByState,
+                value -> cmHandlesByState.get("lockedCmHandlesCount"))
+            .tag(STATE_TAG, "LOCKED")
+            .description("Current number of cm handles in locked state")
+            .register(meterRegistry);
+    }
+
+    /**
+     * Register gauge metric for cm handles with state 'deleting'.
+     *
+     * @param meterRegistry meter registry
+     * @return cm handle state gauge
+     */
+    @Bean
+    public Gauge deletingCmHandles(final MeterRegistry meterRegistry) {
+        return Gauge.builder(CM_HANDLE_STATE_GAUGE, cmHandlesByState,
+                value -> cmHandlesByState.get("deletingCmHandlesCount"))
+            .tag(STATE_TAG, "DELETING")
+            .description("Current number of cm handles in deleting state")
+            .register(meterRegistry);
+    }
+
+    /**
+     * Register gauge metric for cm handles with state 'deleted'.
+     *
+     * @param meterRegistry meter registry
+     * @return cm handle state gauge
+     */
+    @Bean
+    public Gauge deletedCmHandles(final MeterRegistry meterRegistry) {
+        return Gauge.builder(CM_HANDLE_STATE_GAUGE, cmHandlesByState,
+                value -> cmHandlesByState.get("deletedCmHandlesCount"))
+            .tag(STATE_TAG, "DELETED")
+            .description("Number of cm handles that have been deleted since the application started")
+            .register(meterRegistry);
+    }
+}
index 708508e..3d8e8b6 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- * Copyright (C) 2025 Nordix Foundation.
+ * Copyright (C) 2025 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.
@@ -31,12 +31,14 @@ import org.onap.cps.ncmp.api.inventory.models.CompositeState;
 import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService;
 import org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventsCmHandleStateHandlerImpl.CmHandleTransitionPair;
 import org.onap.cps.ncmp.utils.events.NcmpInventoryModelOnboardingFinishedEvent;
+import org.springframework.context.annotation.DependsOn;
 import org.springframework.context.event.EventListener;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Component;
 
 @Component
 @RequiredArgsConstructor
+@DependsOn("adminCacheConfig")
 @Slf4j
 public class CmHandleStateMonitor {
     private static final String METRIC_POSTFIX = "CmHandlesCount";
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/CmHandleStateGaugeConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/CmHandleStateGaugeConfigSpec.groovy
new file mode 100644 (file)
index 0000000..499f8b8
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.config
+
+import com.hazelcast.map.IMap
+import io.micrometer.core.instrument.MeterRegistry
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry
+import org.onap.cps.ncmp.impl.cache.AdminCacheConfig
+import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService
+import org.onap.cps.ncmp.impl.inventory.sync.lcm.CmHandleStateMonitor
+import org.spockframework.spring.SpringBean
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.context.ContextConfiguration
+import org.springframework.test.context.TestPropertySource
+import spock.lang.Specification
+
+@SpringBootTest(classes = [CmHandleStateGaugeConfig, CmHandleStateMonitor, AdminCacheConfig])
+@ContextConfiguration(classes = [CpsApplicationContext])
+@TestPropertySource(properties = ["hazelcast.mode.kubernetes.enabled=false"])
+class CmHandleStateGaugeConfigSpec extends Specification {
+
+    @Autowired
+    CpsApplicationContext cpsApplicationContext
+    @SpringBean
+    CmHandleQueryService cmHandleQueryService = Mock()
+    @SpringBean
+    MeterRegistry meterRegistry = Mock()
+
+    def cmHandlesByState = Mock(IMap)
+    def objectUnderTest = new CmHandleStateGaugeConfig(cmHandlesByState)
+    def simpleMeterRegistry = new SimpleMeterRegistry()
+
+    def 'Creating gauges for cm handle states.'() {
+        given: 'cache returns a test value (123) for each state'
+            cmHandlesByState.get(_) >> 123
+        when: 'gauges for each state are created'
+            objectUnderTest.advisedCmHandles(simpleMeterRegistry)
+            objectUnderTest.readyCmHandles(simpleMeterRegistry)
+            objectUnderTest.lockedCmHandles(simpleMeterRegistry)
+            objectUnderTest.deletingCmHandles(simpleMeterRegistry)
+            objectUnderTest.deletedCmHandles(simpleMeterRegistry)
+        then: 'each state has the correct value when queried'
+            ['ADVISED', 'READY', 'LOCKED', 'DELETING', 'DELETED'].each { state ->
+                def gaugeValue = simpleMeterRegistry.get(objectUnderTest.CM_HANDLE_STATE_GAUGE).tag('state',state).gauge().value()
+                assert gaugeValue == 123
+            }
+    }
+
+    def 'Controlling order of bean initialization'() {
+        when: 'cm handle state gauge config is retrieved'
+            cpsApplicationContext.getCpsBean(CmHandleStateGaugeConfig.class)
+        then: 'cm handle state monitor should already be available'
+            cpsApplicationContext.getCpsBean(CmHandleStateMonitor.class) != null
+    }
+
+}