Skip to content

Commit

Permalink
[RHCLOUD-35690] Add Kessel successes and Failures metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
g-duval committed Jan 13, 2025
1 parent b13fecf commit e5e2bae
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ public class KesselAuthorization {
* particular permission for a subject.
*/
private static final String KESSEL_METRICS_PERMISSION_CHECK_TIMER_NAME = "notifications.kessel.relationships.permission.check.requests";
/**
* Represents the counter name to count permission check requests.
*/
public static final String KESSEL_METRICS_PERMISSION_CHECK_COUNTER_NAME = "notifications.kessel.relationships.permission.check.count";
/**
* Represents the counter name to count lookup resources requests.
*/
public static final String KESSEL_METRICS_LOOKUP_RESOURCES_COUNTER_NAME = "notifications.kessel.relationships.lookup.check.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
CheckClient checkClient;
Expand Down Expand Up @@ -102,12 +114,14 @@ public void hasPermissionOnResource(final SecurityContext securityContext, final
"[identity: %s][permission: %s][resource_type: %s][resource_id: %s] Unable to query Kessel for a permission on a resource",
identity, permission, resourceType, resourceId
);

meterRegistry.counter(KESSEL_METRICS_PERMISSION_CHECK_COUNTER_NAME, Tags.of(COUNTER_TAG_REQUEST_RESULT, COUNTER_TAG_FAILURES)).increment();
throw e;
} finally {
// Stop the timer.
permissionCheckTimer.stop(this.meterRegistry.timer(KESSEL_METRICS_PERMISSION_CHECK_TIMER_NAME, Tags.of(KESSEL_METRICS_TAG_PERMISSION_KEY, permission.getKesselPermissionName(), Constants.KESSEL_METRICS_TAG_RESOURCE_TYPE_KEY, resourceType.name())));
}

// Stop the timer.
permissionCheckTimer.stop(this.meterRegistry.timer(KESSEL_METRICS_PERMISSION_CHECK_TIMER_NAME, Tags.of(KESSEL_METRICS_TAG_PERMISSION_KEY, permission.getKesselPermissionName(), Constants.KESSEL_METRICS_TAG_RESOURCE_TYPE_KEY, resourceType.name())));
meterRegistry.counter(KESSEL_METRICS_PERMISSION_CHECK_COUNTER_NAME, Tags.of(COUNTER_TAG_REQUEST_RESULT, COUNTER_TAG_SUCCESSES)).increment();

Log.tracef("[identity: %s][permission: %s][resource_type: %s][resource_id: %s] Received payload for the permission check: %s", identity, permission, resourceType, resourceId, response);

Expand Down Expand Up @@ -152,12 +166,15 @@ public Set<UUID> lookupAuthorizedIntegrations(final SecurityContext securityCont
"[identity: %s][permission: %s][resource_type: %s] Runtime error when querying Kessel for integration resources with request payload: %s",
identity, integrationPermission, ResourceType.INTEGRATION, request
);
meterRegistry.counter(KESSEL_METRICS_LOOKUP_RESOURCES_COUNTER_NAME, Tags.of(COUNTER_TAG_REQUEST_RESULT, COUNTER_TAG_FAILURES)).increment();

throw e;
} finally {
// Stop the timer.
lookupTimer.stop(this.meterRegistry.timer(KESSEL_METRICS_LOOKUP_RESOURCES_TIMER_NAME, Tags.of(KESSEL_METRICS_TAG_PERMISSION_KEY, integrationPermission.getKesselPermissionName(), Constants.KESSEL_METRICS_TAG_RESOURCE_TYPE_KEY, ResourceType.INTEGRATION.name())));
}

// Stop the timer.
lookupTimer.stop(this.meterRegistry.timer(KESSEL_METRICS_LOOKUP_RESOURCES_TIMER_NAME, Tags.of(KESSEL_METRICS_TAG_PERMISSION_KEY, integrationPermission.getKesselPermissionName(), Constants.KESSEL_METRICS_TAG_RESOURCE_TYPE_KEY, ResourceType.INTEGRATION.name())));
meterRegistry.counter(KESSEL_METRICS_LOOKUP_RESOURCES_COUNTER_NAME, Tags.of(COUNTER_TAG_REQUEST_RESULT, COUNTER_TAG_SUCCESSES)).increment();

// Process the incoming responses.
final Set<UUID> uuids = new HashSet<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.redhat.cloud.notifications.auth.kessel;

import com.redhat.cloud.notifications.MicrometerAssertionHelper;
import com.redhat.cloud.notifications.auth.kessel.permission.IntegrationPermission;
import com.redhat.cloud.notifications.auth.kessel.permission.KesselPermission;
import com.redhat.cloud.notifications.auth.kessel.permission.WorkspacePermission;
Expand All @@ -16,7 +17,9 @@
import jakarta.ws.rs.ForbiddenException;
import jakarta.ws.rs.NotFoundException;
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.relations.v1beta1.CheckRequest;
Expand All @@ -32,6 +35,11 @@
import java.util.Set;
import java.util.UUID;

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 com.redhat.cloud.notifications.auth.kessel.KesselAuthorization.KESSEL_METRICS_LOOKUP_RESOURCES_COUNTER_NAME;
import static com.redhat.cloud.notifications.auth.kessel.KesselAuthorization.KESSEL_METRICS_PERMISSION_CHECK_COUNTER_NAME;
import static org.mockito.ArgumentMatchers.anyString;

@QuarkusTest
Expand All @@ -48,22 +56,23 @@ public class KesselAuthorizationTest {
@Inject
KesselAuthorization kesselAuthorization;

@Inject
MicrometerAssertionHelper micrometerAssertionHelper;

@BeforeEach
void beforeEach() {
// save counter values
saveCounterValues();
}

/**
* Tests that when the principal is authorized, the function under test
* does not raise an exception.
*/
@Test
void testAuthorized() {
// 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 = initMockedSecurityContextWithRhIdentity();

// Enable the Kessel back end integration for this test.
Mockito.when(this.backendConfig.isKesselRelationsEnabled(anyString())).thenReturn(true);
Expand All @@ -82,6 +91,50 @@ void testAuthorized() {

// Verify that we called Kessel.
Mockito.verify(this.checkClient, Mockito.times(1)).check(Mockito.any());

// Assert counter values
assertCounterIncrements(1, 0, 0, 0);
}

/**
* Tests failure counter increments in case of exception.
*/
@Test
void testFailureCounterIncrements() {
// Mock the security context.
final SecurityContext mockedSecurityContext = initMockedSecurityContextWithRhIdentity();

// Simulate that Kessel returns an exception
Mockito.when(this.checkClient.check(Mockito.any())).thenThrow(RuntimeException.class);

// Call the function under test.
Assertions.assertThrows(
RuntimeException.class,
() -> this.kesselAuthorization.hasPermissionOnResource(
mockedSecurityContext,
WorkspacePermission.EVENT_LOG_VIEW,
ResourceType.WORKSPACE,
"workspace-uuid"
)
);

// Assert counter values
assertCounterIncrements(0, 1, 0, 0);

// 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,
() -> this.kesselAuthorization.lookupAuthorizedIntegrations(mockedSecurityContext, IntegrationPermission.VIEW)
);

// Assert counter values
assertCounterIncrements(0, 0, 0, 1);
}

/**
Expand All @@ -92,15 +145,7 @@ void testAuthorized() {
@Test
void testHasPermissionOnIntegration() {
// 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 = initMockedSecurityContextWithRhIdentity();

// Simulate an "authorized" response from Kessel.
final CheckResponse positiveCheckResponse = CheckResponse.newBuilder().setAllowed(CheckResponse.Allowed.ALLOWED_TRUE).build();
Expand All @@ -118,15 +163,7 @@ void testHasPermissionOnIntegration() {
@Test
void testHasPermissionOnIntegrationThrowsNotFoundException() {
// 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 = initMockedSecurityContextWithRhIdentity();

// Simulate an "unauthorized" response coming from Kessel.
final CheckResponse negativeCheckResponse = CheckResponse.newBuilder().setAllowed(CheckResponse.Allowed.ALLOWED_FALSE).build();
Expand All @@ -137,6 +174,9 @@ void testHasPermissionOnIntegrationThrowsNotFoundException() {
NotFoundException.class,
() -> this.kesselAuthorization.hasPermissionOnIntegration(mockedSecurityContext, IntegrationPermission.VIEW, UUID.randomUUID())
);

// Assert counter values
assertCounterIncrements(1, 0, 0, 0);
}

/**
Expand All @@ -146,15 +186,7 @@ void testHasPermissionOnIntegrationThrowsNotFoundException() {
@Test
void testUnauthorized() {
// 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 = initMockedSecurityContextWithRhIdentity();

// Enable the Kessel back end integration for this test.
Mockito.when(this.backendConfig.isKesselRelationsEnabled(anyString())).thenReturn(true);
Expand All @@ -178,6 +210,9 @@ void testUnauthorized() {

// Verify that we called Kessel.
Mockito.verify(this.checkClient, Mockito.times(1)).check(Mockito.any());

// Assert counter values
assertCounterIncrements(1, 0, 0, 0);
}

/**
Expand All @@ -187,15 +222,7 @@ void testUnauthorized() {
@Test
void testLookupAuthorizedIntegrations() {
// 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 = initMockedSecurityContextWithRhIdentity();

// Enable the Kessel back end integration for this test.
Mockito.when(this.backendConfig.isKesselRelationsEnabled(anyString())).thenReturn(true);
Expand All @@ -218,15 +245,38 @@ void testLookupAuthorizedIntegrations() {
final List<LookupResourcesResponse> 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<UUID> result = this.kesselAuthorization.lookupAuthorizedIntegrations(mockedSecurityContext, IntegrationPermission.VIEW);

// Assert counter values
assertCounterIncrements(0, 0, 1, 0);

// Assert that the result is the expected one.
final Set<UUID> expectedUuids = Set.of(firstUuid, secondUuid, thirdUuid);

result.forEach(r -> Assertions.assertTrue(expectedUuids.contains(r), String.format("UUID \"%s\" not present in the expected UUIDs", r)));
}

/**
* 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;
}

/**
* Test that permission check requests are properly built for both service
* accounts and users.
Expand Down Expand Up @@ -326,4 +376,18 @@ public String toString() {
Assertions.assertEquals(ResourceType.INTEGRATION.getKesselObjectType(), lookupResourcesRequest.getResourceType(), String.format("unexpected resource type obtained on test case: %s", tc));
}
}

private void saveCounterValues() {
this.micrometerAssertionHelper.saveCounterValueFilteredByTagsBeforeTest(KESSEL_METRICS_PERMISSION_CHECK_COUNTER_NAME, COUNTER_TAG_REQUEST_RESULT, COUNTER_TAG_SUCCESSES);
this.micrometerAssertionHelper.saveCounterValueFilteredByTagsBeforeTest(KESSEL_METRICS_PERMISSION_CHECK_COUNTER_NAME, COUNTER_TAG_REQUEST_RESULT, COUNTER_TAG_FAILURES);
this.micrometerAssertionHelper.saveCounterValueFilteredByTagsBeforeTest(KESSEL_METRICS_LOOKUP_RESOURCES_COUNTER_NAME, COUNTER_TAG_REQUEST_RESULT, COUNTER_TAG_SUCCESSES);
this.micrometerAssertionHelper.saveCounterValueFilteredByTagsBeforeTest(KESSEL_METRICS_LOOKUP_RESOURCES_COUNTER_NAME, COUNTER_TAG_REQUEST_RESULT, COUNTER_TAG_FAILURES);
}

private void assertCounterIncrements(final int expectedPermissionCheckSuccesses, final int expectedPermissionCheckFailures, final int expectedLookupResourcesSuccesses, int expectedLookupResourcesFailures) {
this.micrometerAssertionHelper.assertCounterValueFilteredByTagsIncrement(KESSEL_METRICS_PERMISSION_CHECK_COUNTER_NAME, COUNTER_TAG_REQUEST_RESULT, COUNTER_TAG_SUCCESSES, expectedPermissionCheckSuccesses);
this.micrometerAssertionHelper.assertCounterValueFilteredByTagsIncrement(KESSEL_METRICS_PERMISSION_CHECK_COUNTER_NAME, COUNTER_TAG_REQUEST_RESULT, COUNTER_TAG_FAILURES, expectedPermissionCheckFailures);
this.micrometerAssertionHelper.assertCounterValueFilteredByTagsIncrement(KESSEL_METRICS_LOOKUP_RESOURCES_COUNTER_NAME, COUNTER_TAG_REQUEST_RESULT, COUNTER_TAG_SUCCESSES, expectedLookupResourcesSuccesses);
this.micrometerAssertionHelper.assertCounterValueFilteredByTagsIncrement(KESSEL_METRICS_LOOKUP_RESOURCES_COUNTER_NAME, COUNTER_TAG_REQUEST_RESULT, COUNTER_TAG_FAILURES, expectedLookupResourcesFailures);
}
}

0 comments on commit e5e2bae

Please sign in to comment.