Skip to content

Commit

Permalink
Add connection timeout support with other modifications
Browse files Browse the repository at this point in the history
 - Get tokens with token binding
 - Add j2 configs
 - Modify AIHttpUtil tests to use call mockwebserver
  • Loading branch information
sahandilshan committed Jan 8, 2025
1 parent cdad525 commit 73a9b36
Show file tree
Hide file tree
Showing 13 changed files with 377 additions and 211 deletions.
14 changes: 12 additions & 2 deletions components/ai-services-mgt/org.wso2.carbon.ai.service.mgt/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<parent>
<groupId>org.wso2.carbon.identity.framework</groupId>
<artifactId>ai-services-mgt</artifactId>
<version>7.6.10-SNAPSHOT</version>
<version>7.7.85-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down Expand Up @@ -72,6 +72,12 @@
<artifactId>log4j-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.ops4j.pax.logging</groupId>
<artifactId>pax-logging-api</artifactId>
Expand Down Expand Up @@ -110,7 +116,11 @@
org.apache.http.util; version="${httpcore.version.osgi.import.range}",
org.apache.http.impl.client; version="${httpcomponents-httpclient.imp.pkg.version.range}",
org.apache.http.impl.nio.client; version="${httpasyncclient.version.osgi.import.range}",
org.apache.http.impl.nio.reactor; version="${httpasyncclient.version.osgi.import.range}",
org.apache.http.impl.nio.conn; version="${httpasyncclient.version.osgi.import.range}",
org.apache.http.concurrent; version="${httpcore.version.osgi.import.range}",
org.apache.http.nio.reactor; version="${httpasyncclient.version.osgi.import.range}",
org.apache.http.nio.conn; version="${httpasyncclient.version.osgi.import.range}",
</Import-Package>
<Export-Package>
org.wso2.carbon.ai.service.mgt.*; version="${carbon.identity.package.export.version}"
Expand Down Expand Up @@ -179,7 +189,7 @@
<limit implementation="org.jacoco.report.check.Limit">
<counter>COMPLEXITY</counter>
<value>COVEREDRATIO</value>
<minimum>0.82</minimum>
<minimum>0.77</minimum>
</limit>
</limits>
</rule>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com).
* Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
Expand Down Expand Up @@ -28,6 +28,7 @@ public class AIConstants {
public static final String AI_TOKEN_SERVICE_MAX_RETRIES_PROPERTY_NAME = "AIServices.TokenRequestMaxRetries";
public static final String AI_TOKEN_SERVICE_TIMEOUT_PROPERTY_NAME = "AIServices.TokenRequestTimeout";
public static final String HTTP_CONNECTION_POOL_SIZE_PROPERTY_NAME = "AIServices.HTTPConnectionPoolSize";
public static final String HTTP_CONNECTION_TIMEOUT_PROPERTY_NAME = "AIServices.HTTPConnectionTimeout";

// Http constants.
public static final String HTTP_BASIC = "Basic";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com).
* Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com).
* Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com).
* Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
Expand Down Expand Up @@ -42,6 +42,7 @@
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

Expand Down Expand Up @@ -192,7 +193,8 @@ public String requestAccessToken() throws AIServerException {
post.setHeader(AUTHORIZATION, HTTP_BASIC + " " + key);
post.setHeader(HEADER_CONTENT_TYPE, CONTENT_TYPE_FORM_URLENCODED);

StringEntity entity = new StringEntity("grant_type=client_credentials");
StringEntity entity = new StringEntity("grant_type=client_credentials&tokenBindingId=" +
UUID.randomUUID());
entity.setContentType(new BasicHeader(HTTP.CONTENT_TYPE, CONTENT_TYPE_FORM_URLENCODED));
post.setEntity(entity);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com).
* Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
Expand Down Expand Up @@ -30,6 +30,10 @@
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.nio.reactor.ConnectingIOReactor;
import org.apache.http.util.EntityUtils;
import org.wso2.carbon.ai.service.mgt.exceptions.AIClientException;
import org.wso2.carbon.ai.service.mgt.exceptions.AIServerException;
Expand All @@ -52,6 +56,7 @@
import static org.wso2.carbon.ai.service.mgt.constants.AIConstants.ErrorMessages.UNABLE_TO_ACCESS_AI_SERVICE_WITH_RENEW_ACCESS_TOKEN;
import static org.wso2.carbon.ai.service.mgt.constants.AIConstants.HTTP_BEARER;
import static org.wso2.carbon.ai.service.mgt.constants.AIConstants.HTTP_CONNECTION_POOL_SIZE_PROPERTY_NAME;
import static org.wso2.carbon.ai.service.mgt.constants.AIConstants.HTTP_CONNECTION_TIMEOUT_PROPERTY_NAME;
import static org.wso2.carbon.ai.service.mgt.constants.AIConstants.TENANT_CONTEXT_PREFIX;

/**
Expand All @@ -65,6 +70,9 @@ public class AIHttpClientUtil {
private static final int HTTP_CONNECTION_POOL_SIZE = IdentityUtil.getProperty(
HTTP_CONNECTION_POOL_SIZE_PROPERTY_NAME) != null ? Integer.parseInt(IdentityUtil.getProperty(
HTTP_CONNECTION_POOL_SIZE_PROPERTY_NAME)) : 20;
private static final int HTTP_CONNECTION_TIMEOUT = IdentityUtil.getProperty(
HTTP_CONNECTION_TIMEOUT_PROPERTY_NAME) != null ? Integer.parseInt(IdentityUtil.getProperty(
HTTP_CONNECTION_TIMEOUT_PROPERTY_NAME)) : 60000; // Making the default timeout 60 seconds.


// Singleton instance of CloseableHttpAsyncClient with connection pooling.
Expand All @@ -74,6 +82,7 @@ public class AIHttpClientUtil {
// Configure the IO reactor.
IOReactorConfig ioReactorConfig = IOReactorConfig.custom()
.setIoThreadCount(Runtime.getRuntime().availableProcessors())
.setConnectTimeout(HTTP_CONNECTION_TIMEOUT)
.build();
ConnectingIOReactor ioReactor;
try {
Expand Down Expand Up @@ -127,7 +136,7 @@ public static Map<String, Object> executeRequest(String aiServiceEndpoint, Strin

HttpUriRequest request = createRequest(aiServiceEndpoint + TENANT_CONTEXT_PREFIX + orgName + path,
requestType, accessToken, requestBody);
HttpResponseWrapper aiServiceResponse = executeRequestWithRetry(httpClient, request);
HttpResponseWrapper aiServiceResponse = executeRequestWithRetry(request);

int statusCode = aiServiceResponse.getStatusCode();
String responseBody = aiServiceResponse.getResponseBody();
Expand Down Expand Up @@ -174,21 +183,19 @@ private static HttpUriRequest createRequest(String url, Class<? extends HttpUriR
return request;
}

protected static HttpResponseWrapper executeRequestWithRetry(CloseableHttpAsyncClient client,
HttpUriRequest request)
protected static HttpResponseWrapper executeRequestWithRetry(HttpUriRequest request)
throws InterruptedException, ExecutionException, IOException, AIServerException {

HttpResponseWrapper response = executeHttpRequest(client, request);
HttpResponseWrapper response = executeHttpRequest(request);

if (response.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
String newAccessToken = AIAccessTokenManager.getInstance().getAccessToken(true);
if (newAccessToken == null) {
throw new AIServerException("Failed to renew access token.", ERROR_RETRIEVING_ACCESS_TOKEN.getCode());
}
request.setHeader(AUTHORIZATION, "Bearer " + newAccessToken);
response = executeHttpRequest(client, request);
request.setHeader(AUTHORIZATION, HTTP_BEARER + " " + newAccessToken);
response = executeHttpRequest(request);
}

return response;
}

Expand Down Expand Up @@ -219,10 +226,11 @@ private static Map<String, Object> convertJsonStringToMap(String jsonString) thr
}
}

protected static HttpResponseWrapper executeHttpRequest(CloseableHttpAsyncClient client, HttpUriRequest httpRequest)
protected static HttpResponseWrapper executeHttpRequest(HttpUriRequest httpRequest)
throws InterruptedException, ExecutionException, IOException, AIServerException {

Future<HttpResponse> apiResponse = client.execute(httpRequest, new FutureCallback<HttpResponse>() {
Future<HttpResponse> apiResponse = AIHttpClientUtil.httpClient.execute(httpRequest,
new FutureCallback<HttpResponse>() {
@Override
public void completed(HttpResponse response) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@
import org.wso2.carbon.ai.service.mgt.exceptions.AIServerException;

import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Base64;
import java.util.concurrent.Future;

import static org.junit.Assert.assertEquals;
Expand Down Expand Up @@ -68,17 +70,18 @@ public class AIAccessTokenManagerTest {
private AIAccessTokenManager tokenManager;
private TestAccessTokenRequestHelper testHelper;
private AIAccessTokenManager.AccessTokenRequestHelper helper;
private CountDownLatch latch;

@BeforeMethod
public void setUp() {
public void setUp() throws NoSuchFieldException, IllegalAccessException {

MockitoAnnotations.openMocks(this);
testHelper = new TestAccessTokenRequestHelper(mockHttpClient);
String key = Base64.getEncoder().encodeToString("testClientId:testClientSecret".getBytes());
assignAIKey(key);
tokenManager = AIAccessTokenManager.getInstance();
tokenManager.setAccessTokenRequestHelper(testHelper);
helper = new AIAccessTokenManager.AccessTokenRequestHelper("key", "endpoint", mockHttpClient);
latch = new CountDownLatch(1);

helper = new AIAccessTokenManager.AccessTokenRequestHelper(key, "endpoint", mockHttpClient);
}

@AfterMethod
Expand Down Expand Up @@ -210,6 +213,7 @@ public void testCancelledScenario() throws Exception {

@Test(expectedExceptions = AIServerException.class)
public void testRequestAccessToken_IOException() throws Exception {

CloseableHttpAsyncClient mockClient = mock(CloseableHttpAsyncClient.class);
doThrow(new IOException("Test IOException")).when(mockClient).close();

Expand Down Expand Up @@ -250,4 +254,22 @@ public String requestAccessToken() throws AIServerException {
}
}
}

private static void assignAIKey(String key) throws NoSuchFieldException, IllegalAccessException {

// Target class and field.
Class<?> targetClass = AIAccessTokenManager.class;
Field aiKeyField = targetClass.getDeclaredField("AI_KEY");

// Make the field accessible.
aiKeyField.setAccessible(true);

// Remove the "final" modifier.
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(aiKeyField, aiKeyField.getModifiers() & ~Modifier.FINAL);

// Set the new value.
aiKeyField.set(null, key); // null because it's a static field.
}
}
Loading

0 comments on commit 73a9b36

Please sign in to comment.