Skip to content

Commit

Permalink
21 configuration providers with caches should be able to recognized b…
Browse files Browse the repository at this point in the history
…y the driver (#26)

* fix issue#21

* add tests for issue#21

* use getSecretCredentialClient instead of DefaultAzureCredential & add try block to Connection

* remove the @disable in AzureAppConfigurationProviderURLParserTest, which is set by mistake in history
  • Loading branch information
norah-li authored May 10, 2024
1 parent 25891e6 commit 2f0adf3
Show file tree
Hide file tree
Showing 4 changed files with 245 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import com.azure.security.keyvault.secrets.SecretClient;
import com.azure.security.keyvault.secrets.SecretClientBuilder;
import oracle.jdbc.OracleConnection;
import oracle.jdbc.spi.OracleConfigurationCachableProvider;
import oracle.jdbc.util.OracleConfigurationCache;
import oracle.jdbc.util.OracleConfigurationProviderNetworkError;
import oracle.jdbc.spi.OracleConfigurationProvider;
Expand All @@ -67,7 +68,7 @@
* </p>
*/
public class AzureAppConfigurationProvider
implements OracleConfigurationProvider {
implements OracleConfigurationCachableProvider {
private static final OracleJsonFactory JSON_FACTORY = new OracleJsonFactory();

private static final String CONNECT_DESCRIPTOR_PROPERTIES_NAME =
Expand Down Expand Up @@ -270,6 +271,12 @@ private Properties refreshProperties(String location)
throw new OracleConfigurationProviderNetworkError(e);
}
}

@Override
public Properties removeProperties(String location) {
Properties deletedProp = cache.remove(location);
return deletedProp;
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package oracle.jdbc.provider.azure.configuration;

import com.azure.data.appconfiguration.ConfigurationClient;
import com.azure.data.appconfiguration.ConfigurationClientBuilder;
import com.azure.data.appconfiguration.models.ConfigurationSetting;
import com.azure.identity.ClientSecretCredentialBuilder;
import oracle.jdbc.datasource.impl.OracleDataSource;
import oracle.jdbc.provider.TestProperties;
import oracle.jdbc.provider.azure.AzureTestProperty;
import oracle.jdbc.provider.azure.authentication.AzureAuthenticationMethod;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import static org.junit.jupiter.api.Assertions.*;

class AzureAppConfigurationProviderTest {

/**
* Verify that the cache is purged after hitting 1017 error.
* Specifically, get connection to the same url twice, but modify the 'user'
* every time.
* The provided app configuration should have correct username, password and
* correct connect descriptor to connect to Database.
**/
@Test
public void testCachePurged() throws SQLException {
ConfigurationClient client = getSecretCredentialClient();
String APP_CONFIG_NAME=
TestProperties.getOrAbort(AzureTestProperty.AZURE_APP_CONFIG_NAME);
String APP_CONFIG_KEY =
TestProperties.getOrAbort(AzureTestProperty.AZURE_APP_CONFIG_KEY);
String APP_CONFIG_LABEL =
TestProperties.getOrAbort(AzureTestProperty.AZURE_APP_CONFIG_LABEL);
String username = "user";
String originalUrl =
"jdbc:oracle:thin:@config-azure://" + APP_CONFIG_NAME +
"?key=" + APP_CONFIG_KEY + "&label=" + APP_CONFIG_LABEL;

String url = composeUrlWithServicePrincipleAuthentication(originalUrl);

// Retrieve original value of 'user'
String originalKeyValue =
client.getConfigurationSetting(APP_CONFIG_KEY + username,
APP_CONFIG_LABEL).getValue();

// Set value of 'user' wrong
client.setConfigurationSetting( APP_CONFIG_KEY + username,
APP_CONFIG_LABEL, originalKeyValue + "wrong");

// Connection fails: hit 1017
SQLException exception = assertThrows(SQLException.class,
() -> tryConnection(url), "Should throw an SQLException");
Assertions.assertEquals(exception.getErrorCode(), 1017);

// Set value of 'user' correct
ConfigurationSetting result =
client.setConfigurationSetting(APP_CONFIG_KEY + username,
APP_CONFIG_LABEL, originalKeyValue);
Assertions.assertEquals(originalKeyValue, result.getValue());

// Connection succeeds
try (Connection conn = tryConnection(url)) {
Assertions.assertNotNull(conn);

Statement st = conn.createStatement();
ResultSet rs = st.executeQuery("SELECT 'Hello, db' FROM sys.dual");
Assertions.assertNotNull(rs.next());
Assertions.assertEquals("Hello, db", rs.getString(1));
}
}

/**
* Helper function: try to get connection form specified url
**/
private Connection tryConnection(String url) throws SQLException {
OracleDataSource ds = new OracleDataSource();
ds.setURL(url);
Connection conn = ds.getConnection();
return conn;
}

/**
* Similar to the method in AzureAppConfigurationProviderURLParserTest
**/
private static ConfigurationClient getSecretCredentialClient() {
return new ConfigurationClientBuilder()
.credential( new ClientSecretCredentialBuilder()
.clientId(TestProperties.getOrAbort(AzureTestProperty.AZURE_CLIENT_ID))
.clientSecret(
TestProperties.getOrAbort(AzureTestProperty.AZURE_CLIENT_SECRET))
.tenantId(TestProperties.getOrAbort(AzureTestProperty.AZURE_TENANT_ID))
.build())
.endpoint("https://" + TestProperties.getOrAbort(
AzureTestProperty.AZURE_APP_CONFIG_NAME) + ".azconfig.io")
.buildClient();
}

/**
* Use {@link AzureAuthenticationMethod#SERVICE_PRINCIPLE} as its
* authentication method.
**/
private String composeUrlWithServicePrincipleAuthentication(String originalUrl){
String[] options = new String[] {
"AUTHENTICATION=AZURE_SERVICE_PRINCIPAL",
"AZURE_CLIENT_ID=" + TestProperties.getOrAbort(
AzureTestProperty.AZURE_CLIENT_ID),
"AZURE_CLIENT_SECRET=" + TestProperties.getOrAbort(
AzureTestProperty.AZURE_CLIENT_SECRET),
"AZURE_TENANT_ID=" + TestProperties.getOrAbort(
AzureTestProperty.AZURE_TENANT_ID)};
return String.format("%s&%s", originalUrl, String.join("&", options));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import oracle.jdbc.provider.oci.vault.Secret;
import oracle.jdbc.provider.oci.vault.SecretFactory;
import oracle.jdbc.provider.parameter.ParameterSet;
import oracle.jdbc.spi.OracleConfigurationCachableProvider;
import oracle.jdbc.spi.OracleConfigurationProvider;
import oracle.jdbc.util.OracleConfigurationCache;
import oracle.jdbc.util.OracleConfigurationProviderNetworkError;
Expand All @@ -34,7 +35,7 @@
* </p>
*/
public class OciDatabaseToolsConnectionProvider
implements OracleConfigurationProvider {
implements OracleConfigurationCachableProvider {

private static final String CONFIG_TIME_TO_LIVE =
"config_time_to_live";
Expand Down Expand Up @@ -304,6 +305,12 @@ private Secret requestSecretFromOCI(String secretId) {
.getContent();
}

@Override
public Properties removeProperties(String location) {
Properties deletedProp = cache.remove(location);
return deletedProp;
}

private Properties refreshProperties(String location)
throws OracleConfigurationProviderNetworkError {
try {
Expand All @@ -312,4 +319,5 @@ private Properties refreshProperties(String location)
throw new OracleConfigurationProviderNetworkError(bmcException);
}
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
import com.oracle.bmc.databasetools.requests.CreateDatabaseToolsConnectionRequest;
import com.oracle.bmc.databasetools.requests.DeleteDatabaseToolsConnectionRequest;
import com.oracle.bmc.databasetools.requests.GetDatabaseToolsConnectionRequest;
import com.oracle.bmc.databasetools.requests.UpdateDatabaseToolsConnectionRequest;
import com.oracle.bmc.databasetools.responses.CreateDatabaseToolsConnectionResponse;
import com.oracle.bmc.databasetools.responses.DeleteDatabaseToolsConnectionResponse;
import com.oracle.bmc.databasetools.responses.GetDatabaseToolsConnectionResponse;
import com.oracle.bmc.databasetools.responses.UpdateDatabaseToolsConnectionResponse;
import com.oracle.bmc.http.internal.BaseSyncClient;
import oracle.jdbc.datasource.impl.OracleDataSource;
import oracle.jdbc.provider.TestProperties;
Expand All @@ -33,6 +35,8 @@
import java.util.Arrays;
import java.util.Objects;

import static org.junit.jupiter.api.Assertions.assertThrows;

/**
* Verifies the {@link OciDatabaseToolsConnectionProvider} as implementing
* behavior specified by its JavaDoc.
Expand Down Expand Up @@ -177,6 +181,80 @@ public void testGetPropertiesFromDeletedConnection() {
() -> PROVIDER.getConnectionProperties(ocid));
}

/**
* Verify that the cache is purged after hitting 1017 error.
* Specifically, get connection to the same url twice, but modify the 'user'
* every time.
* A new db tools connection is created and deleted for this test.
**/
@Test
public void testCachePurged() throws SQLException {
String OCI_DISPLAY_NAME = "display_name_for_connection";
String OCI_USERNAME = "admin";
String OCI_PASSWORD_OCID = TestProperties.getOrAbort(
OciTestProperty.OCI_PASSWORD_OCID);
String OCI_DATABASE_OCID = TestProperties.getOrAbort(
OciTestProperty.OCI_DATABASE_OCID);
String OCI_COMPARTMENT_ID = TestProperties.getOrAbort(
OciTestProperty.OCI_COMPARTMENT_ID);
String OCI_DATABASE_CONNECTION_STRING = getConnectionStringFromAutonomousDatabase(
OCI_DATABASE_OCID);

// Create new Connection
CreateDatabaseToolsConnectionResponse createResponse = sendCreateConnRequest(
OCI_USERNAME, OCI_PASSWORD_OCID, OCI_DISPLAY_NAME, OCI_COMPARTMENT_ID,
OCI_DATABASE_CONNECTION_STRING, OCI_DATABASE_OCID);

// The db tools connection is being created.
Assertions.assertEquals(201,
createResponse.get__httpStatusCode__());

// Retrieve OCID of Connection
String ocid = createResponse
.getDatabaseToolsConnection().getId();

// The url used to connect to Database
String url = "jdbc:oracle:thin:@config-ocidbtools://" + ocid;

// Set value of 'user' wrong
UpdateDatabaseToolsConnectionDetails updateDatabaseToolsConnectionDetails =
UpdateDatabaseToolsConnectionOracleDatabaseDetails.builder()
.userName(OCI_USERNAME + "wrong")
.build();

UpdateDatabaseToolsConnectionResponse updateResponse =
sendUpdateConnRequest(ocid, updateDatabaseToolsConnectionDetails);
Assertions.assertEquals(202, updateResponse.get__httpStatusCode__());

// Connection fails: hit 1017
SQLException exception = assertThrows(SQLException.class,
() -> tryConnection(url), "Should throw an SQLException");
Assertions.assertEquals(exception.getErrorCode(), 1017);

// Set value of 'user' correct
UpdateDatabaseToolsConnectionDetails updateDatabaseToolsConnectionDetails2 = UpdateDatabaseToolsConnectionOracleDatabaseDetails.builder()
.userName(OCI_USERNAME).build();

UpdateDatabaseToolsConnectionResponse updateResponse2 =
sendUpdateConnRequest(ocid, updateDatabaseToolsConnectionDetails2);
Assertions.assertEquals(202, updateResponse2.get__httpStatusCode__());

// Connection succeeds
Connection conn = tryConnection(url);
Assertions.assertNotNull(conn);

Statement st = conn.createStatement();
ResultSet rs = st.executeQuery("SELECT 'Hello, db' FROM sys.dual");
Assertions.assertNotNull(rs.next());
Assertions.assertEquals("Hello, db", rs.getString(1));

// Finally delete Connection
DeleteDatabaseToolsConnectionResponse deleteResponse = sendDeleteConnRequest(
ocid);
Assertions.assertEquals(202,
deleteResponse.get__httpStatusCode__()); /* Request accepted. This db tools connection will be deleted */
}

/**
* Helper function: send create DB Tools Connection request
* @param OCI_DATABASE_OCID The OCID of the Autonomous Database
Expand Down Expand Up @@ -268,6 +346,27 @@ private GetDatabaseToolsConnectionResponse sendGetConnRequest(String ocid) {
return response;
}

/**
* Helper function: send update DB Tools Connection request
* @param ocid The OCID of DB Tools Connection
* @param updateDatabaseToolsConnectionDetails
* The updateDatabaseToolsConnectionDetails to update to this
* connection
* @return UpdateDatabaseToolsConnectionResponse
*/
private UpdateDatabaseToolsConnectionResponse sendUpdateConnRequest(String ocid, UpdateDatabaseToolsConnectionDetails updateDatabaseToolsConnectionDetails){
/* Create a request and dependent object(s). */
UpdateDatabaseToolsConnectionRequest updateDatabaseToolsConnectionRequest
= UpdateDatabaseToolsConnectionRequest.builder()
.databaseToolsConnectionId(ocid)
.updateDatabaseToolsConnectionDetails(updateDatabaseToolsConnectionDetails)
.build();

/* Send request to the Client */
UpdateDatabaseToolsConnectionResponse response = client.updateDatabaseToolsConnection(updateDatabaseToolsConnectionRequest);
return response;
}

/**
* Helper function: send get Autonomous Database request
* @param OCI_DATABASE_OCID The OCID of the Autonomous Database
Expand Down Expand Up @@ -300,4 +399,14 @@ private String getConnectionStringFromAutonomousDatabase(
.getValue();
return CONNECTION_STRING;
}

/**
* Helper function: try to get connection form specified url
**/
private Connection tryConnection(String url) throws SQLException {
OracleDataSource ds = new OracleDataSource();
ds.setURL(url);
Connection conn = ds.getConnection();
return conn;
}
}

0 comments on commit 2f0adf3

Please sign in to comment.