From a645cd4878d6b5a0893c34b914ba76750aaf0f1c Mon Sep 17 00:00:00 2001 From: Guillaume Duval Date: Tue, 14 Jan 2025 10:11:16 +0100 Subject: [PATCH] [RHCLOUD-35690] Add Kessel successes and Failures metrics on Kessel Inventory (aka Kessel Asset) --- .../auth/kessel/KesselAssets.java | 23 +++-- .../auth/kessel/KesselAssetsTest.java | 95 +++++++++++++++---- .../auth/kessel/KesselAuthorizationTest.java | 8 +- 3 files changed, 94 insertions(+), 32 deletions(-) diff --git a/backend/src/main/java/com/redhat/cloud/notifications/auth/kessel/KesselAssets.java b/backend/src/main/java/com/redhat/cloud/notifications/auth/kessel/KesselAssets.java index 96fbc86c5f..501473c680 100644 --- a/backend/src/main/java/com/redhat/cloud/notifications/auth/kessel/KesselAssets.java +++ b/backend/src/main/java/com/redhat/cloud/notifications/auth/kessel/KesselAssets.java @@ -26,6 +26,12 @@ public class KesselAssets { */ private static final String KESSEL_METRICS_INVENTORY_INTEGRATION_TIMER_NAME = "notifications.kessel.inventory.resources"; + protected static final String KESSEL_METRICS_INVENTORY_INTEGRATION_COUNTER_NAME = "notifications.kessel.inventory.integration.count"; + + protected static final String COUNTER_TAG_FAILURES = "failures"; + protected static final String COUNTER_TAG_REQUEST_RESULT = "result"; + protected static final String COUNTER_TAG_SUCCESSES = "successes"; + @Inject BackendConfig backendConfig; @@ -63,12 +69,13 @@ public void createIntegration(final SecurityContext securityContext, final Strin "[identity: %s][workspace_id: %s][integration_id: %s] Unable to create integration in Kessel's inventory", SecurityContextUtil.extractRhIdentity(securityContext), workspaceId, integrationId, request ); - + meterRegistry.counter(KESSEL_METRICS_INVENTORY_INTEGRATION_COUNTER_NAME, Tags.of(COUNTER_TAG_REQUEST_RESULT, COUNTER_TAG_FAILURES)).increment(); throw e; + } finally { + // Stop the timer. + createIntegrationTimer.stop(this.meterRegistry.timer(KESSEL_METRICS_INVENTORY_INTEGRATION_TIMER_NAME, Tags.of(Constants.KESSEL_METRICS_TAG_RESOURCE_TYPE_KEY, ResourceType.INTEGRATION.name()))); } - - // Stop the timer. - createIntegrationTimer.stop(this.meterRegistry.timer(KESSEL_METRICS_INVENTORY_INTEGRATION_TIMER_NAME, Tags.of(Constants.KESSEL_METRICS_TAG_RESOURCE_TYPE_KEY, ResourceType.INTEGRATION.name()))); + meterRegistry.counter(KESSEL_METRICS_INVENTORY_INTEGRATION_COUNTER_NAME, Tags.of(COUNTER_TAG_REQUEST_RESULT, COUNTER_TAG_SUCCESSES)).increment(); Log.tracef("[identity: %s][workspace_id: %s][integration_id: %s] Received payload for the integration creation in Kessel's inventory: %s", SecurityContextUtil.extractRhIdentity(securityContext), workspaceId, integrationId, response); Log.debugf("[identity: %s][workspace_id: %s][integration_id: %s] Integration created in Kessel's inventory", SecurityContextUtil.extractRhIdentity(securityContext), workspaceId, integrationId); @@ -102,12 +109,14 @@ public void deleteIntegration(final SecurityContext securityContext, final Strin "[identity: %s][workspace_id: %s][integration_id: %s] Unable to delete integration in Kessel's inventory", SecurityContextUtil.extractRhIdentity(securityContext), workspaceId, integrationId, request ); + meterRegistry.counter(KESSEL_METRICS_INVENTORY_INTEGRATION_COUNTER_NAME, Tags.of(COUNTER_TAG_REQUEST_RESULT, COUNTER_TAG_FAILURES)).increment(); throw e; + } finally { + // Stop the timer. + deleteIntegrationTimer.stop(this.meterRegistry.timer(KESSEL_METRICS_INVENTORY_INTEGRATION_TIMER_NAME, Tags.of(Constants.KESSEL_METRICS_TAG_RESOURCE_TYPE_KEY, ResourceType.INTEGRATION.name()))); } - - // Stop the timer. - deleteIntegrationTimer.stop(this.meterRegistry.timer(KESSEL_METRICS_INVENTORY_INTEGRATION_TIMER_NAME, Tags.of(Constants.KESSEL_METRICS_TAG_RESOURCE_TYPE_KEY, ResourceType.INTEGRATION.name()))); + meterRegistry.counter(KESSEL_METRICS_INVENTORY_INTEGRATION_COUNTER_NAME, Tags.of(COUNTER_TAG_REQUEST_RESULT, COUNTER_TAG_SUCCESSES)).increment(); Log.tracef("[identity: %s][workspace_id: %s][integration_id: %s] Received payload for the integration removal in Kessel's inventory: %s", SecurityContextUtil.extractRhIdentity(securityContext), workspaceId, integrationId, response); Log.debugf("[identity: %s][workspace_id: %s][integration_id: %s] Integration deleted in Kessel's inventory", SecurityContextUtil.extractRhIdentity(securityContext), workspaceId, integrationId); diff --git a/backend/src/test/java/com/redhat/cloud/notifications/auth/kessel/KesselAssetsTest.java b/backend/src/test/java/com/redhat/cloud/notifications/auth/kessel/KesselAssetsTest.java index b82c24b6df..33ec3a5803 100644 --- a/backend/src/test/java/com/redhat/cloud/notifications/auth/kessel/KesselAssetsTest.java +++ b/backend/src/test/java/com/redhat/cloud/notifications/auth/kessel/KesselAssetsTest.java @@ -1,5 +1,6 @@ package com.redhat.cloud.notifications.auth.kessel; +import com.redhat.cloud.notifications.MicrometerAssertionHelper; import com.redhat.cloud.notifications.auth.principal.ConsolePrincipal; import com.redhat.cloud.notifications.auth.principal.rhid.RhIdPrincipal; import com.redhat.cloud.notifications.auth.principal.rhid.RhIdentity; @@ -9,7 +10,9 @@ import io.quarkus.test.junit.mockito.InjectSpy; import jakarta.inject.Inject; import jakarta.ws.rs.core.SecurityContext; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.project_kessel.api.inventory.v1beta1.resources.CreateNotificationsIntegrationRequest; @@ -20,6 +23,11 @@ import java.util.UUID; +import static com.redhat.cloud.notifications.auth.kessel.KesselAssets.KESSEL_METRICS_INVENTORY_INTEGRATION_COUNTER_NAME; +import static com.redhat.cloud.notifications.auth.kessel.KesselAuthorization.COUNTER_TAG_FAILURES; +import static com.redhat.cloud.notifications.auth.kessel.KesselAuthorization.COUNTER_TAG_REQUEST_RESULT; +import static com.redhat.cloud.notifications.auth.kessel.KesselAuthorization.COUNTER_TAG_SUCCESSES; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; @QuarkusTest @@ -33,6 +41,15 @@ public class KesselAssetsTest { @InjectMock NotificationsIntegrationClient notificationsIntegrationClient; + @Inject + MicrometerAssertionHelper micrometerAssertionHelper; + + @BeforeEach + void beforeEach() { + // save counter values + saveCounterValues(); + } + /** * Test that the function under test calls the Kessel inventory to create * the integration. @@ -40,15 +57,7 @@ public class KesselAssetsTest { @Test void testCreateIntegration() { // Mock the security context. - final SecurityContext mockedSecurityContext = Mockito.mock(SecurityContext.class); - - // Create a RhIdentity principal and assign it to the mocked security - // context. - final RhIdentity identity = Mockito.mock(RhIdentity.class); - Mockito.when(identity.getName()).thenReturn("Red Hat user"); - - final ConsolePrincipal principal = new RhIdPrincipal(identity); - Mockito.when(mockedSecurityContext.getUserPrincipal()).thenReturn(principal); + final SecurityContext mockedSecurityContext = this.initMockedSecurityContextWithRhIdentity(); // Enable the Kessel back end integration for this test. Mockito.when(this.backendConfig.isKesselRelationsEnabled(anyString())).thenReturn(true); @@ -58,6 +67,8 @@ void testCreateIntegration() { // Verify that the inventory call was made. Mockito.verify(this.notificationsIntegrationClient, Mockito.times(1)).CreateNotificationsIntegration(Mockito.any(CreateNotificationsIntegrationRequest.class)); + + assertCounterIncrements(1, 0); } /** @@ -67,15 +78,7 @@ void testCreateIntegration() { @Test void testDeleteIntegration() { // Mock the security context. - final SecurityContext mockedSecurityContext = Mockito.mock(SecurityContext.class); - - // Create a RhIdentity principal and assign it to the mocked security - // context. - final RhIdentity identity = Mockito.mock(RhIdentity.class); - Mockito.when(identity.getName()).thenReturn("Red Hat user"); - - final ConsolePrincipal principal = new RhIdPrincipal(identity); - Mockito.when(mockedSecurityContext.getUserPrincipal()).thenReturn(principal); + final SecurityContext mockedSecurityContext = this.initMockedSecurityContextWithRhIdentity(); // Enable the Kessel back end integration for this test. Mockito.when(this.backendConfig.isKesselRelationsEnabled(anyString())).thenReturn(true); @@ -85,6 +88,35 @@ void testDeleteIntegration() { // Verify that the inventory call was made. Mockito.verify(this.notificationsIntegrationClient, Mockito.times(1)).DeleteNotificationsIntegration(Mockito.any(DeleteNotificationsIntegrationRequest.class)); + + assertCounterIncrements(1, 0); + } + + + /** + * Tests failures calling Kessel inventory api + */ + @Test + void testCreateAndDeleteFailures() { + // Mock the security context. + final SecurityContext mockedSecurityContext = this.initMockedSecurityContextWithRhIdentity(); + + Mockito.when(this.notificationsIntegrationClient.CreateNotificationsIntegration(any(CreateNotificationsIntegrationRequest.class))).thenThrow(RuntimeException.class); + Mockito.when(this.notificationsIntegrationClient.DeleteNotificationsIntegration(any(DeleteNotificationsIntegrationRequest.class))).thenThrow(RuntimeException.class); + + // Call the function under test. + Assertions.assertThrows( + RuntimeException.class, + () -> this.kesselAssets.deleteIntegration(mockedSecurityContext, UUID.randomUUID().toString(), UUID.randomUUID().toString()) + ); + assertCounterIncrements(0, 1); + + // Call the function under test. + Assertions.assertThrows( + RuntimeException.class, + () -> this.kesselAssets.createIntegration(mockedSecurityContext, UUID.randomUUID().toString(), UUID.randomUUID().toString()) + ); + assertCounterIncrements(0, 2); } /** @@ -127,4 +159,31 @@ void testBuildDeleteIntegrationRequest() { Assertions.assertEquals(this.backendConfig.getKesselInventoryReporterInstanceId(), reporterData.getReporterInstanceId(), "the \"reporter instance id\" was incorrectly set"); Assertions.assertEquals(ReporterData.ReporterType.NOTIFICATIONS, reporterData.getReporterType(), "the \"reporter type\" was incorrectly set"); } + + /** + * Mock the security context. + */ + private static @NotNull SecurityContext initMockedSecurityContextWithRhIdentity() { + // Mock the security context. + final SecurityContext mockedSecurityContext = Mockito.mock(SecurityContext.class); + + // Create a RhIdentity principal and assign it to the mocked security + // context. + final RhIdentity identity = Mockito.mock(RhIdentity.class); + Mockito.when(identity.getName()).thenReturn("Red Hat user"); + + final ConsolePrincipal principal = new RhIdPrincipal(identity); + Mockito.when(mockedSecurityContext.getUserPrincipal()).thenReturn(principal); + return mockedSecurityContext; + } + + private void saveCounterValues() { + this.micrometerAssertionHelper.saveCounterValueFilteredByTagsBeforeTest(KESSEL_METRICS_INVENTORY_INTEGRATION_COUNTER_NAME, COUNTER_TAG_REQUEST_RESULT, COUNTER_TAG_SUCCESSES); + this.micrometerAssertionHelper.saveCounterValueFilteredByTagsBeforeTest(KESSEL_METRICS_INVENTORY_INTEGRATION_COUNTER_NAME, COUNTER_TAG_REQUEST_RESULT, COUNTER_TAG_FAILURES); + } + + private void assertCounterIncrements(final int expectedSuccesses, final int expectedFailures) { + this.micrometerAssertionHelper.assertCounterValueFilteredByTagsIncrement(KESSEL_METRICS_INVENTORY_INTEGRATION_COUNTER_NAME, COUNTER_TAG_REQUEST_RESULT, COUNTER_TAG_SUCCESSES, expectedSuccesses); + this.micrometerAssertionHelper.assertCounterValueFilteredByTagsIncrement(KESSEL_METRICS_INVENTORY_INTEGRATION_COUNTER_NAME, COUNTER_TAG_REQUEST_RESULT, COUNTER_TAG_FAILURES, expectedFailures); + } } diff --git a/backend/src/test/java/com/redhat/cloud/notifications/auth/kessel/KesselAuthorizationTest.java b/backend/src/test/java/com/redhat/cloud/notifications/auth/kessel/KesselAuthorizationTest.java index 2f02b8f161..1997ba2f3d 100644 --- a/backend/src/test/java/com/redhat/cloud/notifications/auth/kessel/KesselAuthorizationTest.java +++ b/backend/src/test/java/com/redhat/cloud/notifications/auth/kessel/KesselAuthorizationTest.java @@ -124,9 +124,6 @@ void testFailureCounterIncrements() { // Return the exception to simulate a Kessel error. Mockito.when(this.lookupClient.lookupResources(Mockito.any())).thenThrow(RuntimeException.class); - // save counter values - saveCounterValues(); - // Call the function under test. Assertions.assertThrows( RuntimeException.class, @@ -134,7 +131,7 @@ void testFailureCounterIncrements() { ); // Assert counter values - assertCounterIncrements(0, 0, 0, 1); + assertCounterIncrements(0, 1, 0, 1); } /** @@ -245,9 +242,6 @@ void testLookupAuthorizedIntegrations() { final List lookupResourcesResponses = List.of(lookupResourcesResponseOne, lookupResourcesResponseTwo, lookupResourcesResponseThree); Mockito.when(this.lookupClient.lookupResources(Mockito.any())).thenReturn(lookupResourcesResponses.iterator()); - // save counter values - saveCounterValues(); - // Call the function under test. final Set result = this.kesselAuthorization.lookupAuthorizedIntegrations(mockedSecurityContext, IntegrationPermission.VIEW);