Diagnose JDBC proxy connection

Oracle REST Data Services connects to your database through JDBC. When a request is initially received by ORDS, it will use the database credentials in the pool configuration file ( e.g. apex_pu.xml ) to determine which REST enabled schema should be used to execute the corresponding SQL. The database connection for that REST enabled schema is achieved through a Oracle JDBC proxy connection. This uses the connect through concept in Oracle database. When a schema is REST enabled, GRANT CONNECT THROUGH to that schema is given to the ORDS_PUBLIC_USER role.

All good so far. This works across different versions of Oracle database and different deployment topologies. However, in some cases the database configuration can cause this proxy connection feature to fail.

For ORDS, this manifests as a TargetNotAvailableException. The error message looks like this:

The database user for the connection pool named |apex|pu|, is not able to proxy to the schema named HR. This could be a configured restriction on the maximum number of database sessions or an authorization failure.

The cause could be a login trigger, resource constraints on sessions per schema, or something else configured on the database. Trying to diagnose this issue can be problematic because ORDS can only report on the exception it’s getting from JDBC. That exception can be found in the stacktrace:

Caused by: java.sql.SQLException: ORA-01017: invalid username/password; logon denied

To reiterate, this is not an ORDS configuration problem. It is some database configuration causing a problem for Oracle JDBC. In an article earlier this year, Kris Rice covers the same JDBC openProxySession logic and provides steps to use SQLcl to confirm the JDBC behaviour: Debugging ORDS Proxy Connection Issues.

To help diagnose this problem and move closer to determine the root cause on the database side, a reproducible test case without ORDS, using JDBC logging is an essential step. Here’s how to do it…

JDBC Jars

The first step is to get the publicly available JDBC jars for logging. ORDS currently ships with the 19.3 JDBC drivers for Java 1.8. Also known as OJDBC8. Get the ojdbc8 diagnostic jars from https://www.oracle.com/database/technologies/appdev/jdbc-ucp-19c-downloads.html. All you need is ojdbc8_g.jar.

ProxyTest class

The next step is to write a class to use JDBC. This java class makes a JDBC connection to the database using one set of credentials and then attempts to proxy to the specified schema. The credentials and schema don’t have to be ORDS specific but if you have found this article, you’ve probably encountered this problem after installing ORDS and testing with your first REST Enabled schema. The credentials and schema could just as easily be any database account that has CONNECT THROUGH privilege to another schema.

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

import oracle.jdbc.OracleConnection;

public class ProxyTest {
  public static void main(final String args[]) throws SQLException {
    if (args.length < 4) {
      System.out.println("Tests that a user can proxy to another schema. Parameters by position ...");
      System.out.println("\turl      JDBC url to for the database. For example jdbc:oracle:thin:@localhost:1521/orclpdb1");
      System.out.println("\tusername Username for a DB account with connect through privilege");
      System.out.println("\tpassword Password for that DB account");
      System.out.println("\tschema   Name of the schema that the DB account has permission to proxy as");
      System.out.println("");
      System.out.println("Example");
      System.out.println("\tProxyText jdbc:oracle:thin:@localhost:1521/orclpdb1 ORDS_PUBLIC_USER oracle HR");

      return;
    }
    final String url = args[0];
    final String username = args[1];
    final String password = args[2];
    final String schema = args[3];

    try {

      final Connection conn = DriverManager.getConnection(
          url, username, password);

      final Properties prop = new Properties();

      System.out.println("Trying to open proxy connection ...");

      prop.put(OracleConnection.PROXY_USER_NAME, schema);

      ((OracleConnection) conn).openProxySession(OracleConnection.PROXYTYPE_USER_NAME, prop);
      final DatabaseMetaData meta = conn.getMetaData();

      System.out.println("Connection user: " + meta.getUserName());
      System.out.println("Product Version: " + meta.getDatabaseProductVersion());

      ((OracleConnection) conn).close(OracleConnection.PROXY_SESSION);
      System.out.println("Succeeded. Proxy connection closed");

    } catch (final SQLException e) {
      e.printStackTrace();
    }
  }
}

The above class uses the Oracle JDBC openProxySession in the same way that ORDS does. It should reproduce the issue and show the same SQLException. Save the above text in a file called ProxyTest.java in the same directory you have the ojdbc8_g.jar.

Compile

Using JDK 1.8, compile ProxyTest.java to produce ProxyTest.class in the same directory:

javac -classpath ojdbc8_g.jar ProxyTest.java

Why JDK 1.8? It’s the Oracle Java for JDBC that corresponds to the OJDBC8. If using a more recent Oracle Java in production you could use that version but in this example, I’m using JDK 1.8.

Logging configuration

The purpose of this exercise is to get a detailed log of what JDBC is doing, as well as what it is receiving from the database, to get closer to diagnosing the root cause of the proxy connection failure. Let’s create a logging configuration to get all that diagnostic data. We’ll call the file oracledebug.properties and give it the following content:

.level=SEVERE
oracle.jdbc.level=FINEST
oracle.ucp.level=FINEST
jdbcbug.level=FINEST
.handlers=java.util.logging.FileHandler
java.util.logging.FileHandler.level=ALL
java.util.logging.FileHandler.pattern=jdbc.log
java.util.logging.FileHandler.count=1
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter

In summary, this configuration will result in JDBC logging entries at the FINEST level showing up in a file called jdbc.log

Run it

Run the ProxyTest class with parameters to connect to the database, attempt the openProxySession call and log everything to jdbc.log

java -classpath ojdbc8_g.jar:. \
     -Doracle.jdbc.Trace=true \
     -Djava.util.logging.config.file=oracledebug.properties \
     ProxyTest \
     jdbc:oracle:thin:@localhost:1521/orclpdb1 \
     ORDS_PUBLIC_USER \
     MySecretPassword \
     HR

Note the classpath includes ojdbc8.jar and the current working directory. The latter is essential to find ProxyTest.class. The other parameters are:

  • -Doracle.jdbc.Trace=true – this enables the logging
  • -Djava.util.logging.config.file=oracledebug.properties – this says how and what to log
  • ProxyTest – the name of the class to run
  • jdbc:oracle:thin:@localhost:1521/orclpdb1 – the JDBC connection string for the database
  • ORDS_PUBLIC_USER – the database account used for the initial connection.
  • MySecretPassword – the password for the above database account
  • HR – the schema to open a proxy session for

As you would expect, for your environment, some of these parameters will be different. The output should look something like this if there are no issues:

Trying to open proxy connection ...
Connection user: HR
Product Version: Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0
Succeeded. Proxy connection closed

Of course, the only reason you are running this is because the environment has issues and you will see a SQLException stack trace instead. Moreover, you’ll have jdbc.log detailing everything that JDBC did. It will include entries that may get you closer to the root cause of the JDBC openProxySession connection problem:

Dec 09, 2020 12:20:57 PM oracle.jdbc.driver.T4CConnection doProxySession
 FINEST: 6EA12C19 Enter: 1, {PROXY_USER_NAME=HR}
 Dec 09, 2020 12:20:57 PM oracle.jdbc.driver.T4CTTIoauthenticate doOAUTH
 FINEST: 65D6B83B Enter: 1, {PROXY_USER_NAME=HR}, 702, 43458
 Dec 09, 2020 12:20:57 PM oracle.jdbc.driver.DBConversion StringToCharBytes
 FINEST: 4E7DC304 Enter: "HR"
 Dec 09, 2020 12:20:57 PM oracle.jdbc.driver.DBConversion stringToDriverCharBytes
 FINEST:          Enter: "HR", 873
 Dec 09, 2020 12:20:57 PM oracle.jdbc.driver.DBConversion stringToDriverCharBytes
 FINEST:          Return: [B@21b2e768
 Dec 09, 2020 12:20:57 PM oracle.jdbc.driver.DBConversion stringToDriverCharBytes
 FINEST:          Exit: [0.018982 ms]
 Dec 09, 2020 12:20:57 PM oracle.jdbc.driver.DBConversion StringToCharBytes
 FINEST: 4E7DC304 Return: [B@21b2e768
 Dec 09, 2020 12:20:57 PM oracle.jdbc.driver.DBConversion StringToCharBytes
 FINEST: 4E7DC304 Exit: [0.042244 ms]
 Dec 09, 2020 12:20:57 PM oracle.jdbc.driver.T4CTTIoauthenticate doOAUTH
 FINEST: 65D6B83B Enter: [B@21b2e768, "", "", 0, null, true, null, null, null, 702, 43458, null

At the very least, you have a log file to begin a meaningful conversation in the JDBC developer community. In fact, some of these steps and java code are based on forum entries, blog posts from Oracle employees and Oracle Support documents. I’m grateful that such a body of knowledge is easily accessible.

Automation with OSB and ORDS

Oracle REST Data Services implements an Open Service Broker API for each connection pool. More information about the services and plans available through the service broker can be found in the ORDS User Guide – Configuration of Database API with Open Service Broker API Compatible Platforms. This post provides an overview of why an Open Service Broker provided by ORDS could change how you automate the provision of databases and users.

Most software systems require a database for data storage & retrieval and database users to perform operations on that data. Applications can interact with the database through an API specific to the programming language ( such as JDBC ) or REST.

These ORDS OSB services open up new automation possibilities for provisioning databases and users using tools and scripting platforms that developers and system operators will already be familiar with. Scripts to run Oracle DBCA or specific SQL commands can now be replaced with more platform specific artifacts that are used with the tools for that platform. For example, to creat a new User Acceptance Test pluggable database instance by cloning:

kubectl create -f clone-user-acceptance-test-instance.yaml

Where the clone-user-acceptance-test-instance.yaml might look like this:

apiVersion: servicecatalog.k8s.io/v1beta1
   kind: ServiceInstance
   metadata:
     name: pdbtest1-instance
     namespace: ords-osb
   spec:
     clusterServiceClassExternalName: create-pluggable-database
     clusterServicePlanExternalName: clone-database
     parameters:
       pdb_name: pdbtest1
       src_pdb_name: db0918_pdb1

In the above case, the create-pluggable-database service has been defined in the ‘ords-osb’ namespace and the administrator has not had to share the credentials . Sensitive information can be managed in a manner that is more suitable for the platform. The Kubernetes Service Catalog topics for operators provides more information on best practice for this.

Cloning existing pluggable databases is likely to be the fastest way to provision new test environments because the source database can have all the necessary object structures, seed data and users for the application to be tested. Depending on the application there might be more database users required too. ORDS can not only provision pluggable databases through it’s open service broker interface, but also database users and those database users can be REST enabled too!

If you’re already using kubernetes or cloud foundry as part of your development or production infrastructure then take a look at how you can do more with your existing Oracle databases through Oracle REST Database Services.

Get notified about new releases

In a previous post I mentioned how to check the version of ORDS you’re using. How do you findout that there is a new release of Oracle REST Data Services available?

One option is to follow @OracleREST on twitter. You’ll get more than just updates on releases too so it’s well worth it.

Another option is to use a website monitoring service to track the download page. The URL could change, but for now, it’s https://www.oracle.com/database/technologies/appdev/rest-data-services-downloads.html

As an example, here are the steps to use the visualping free service plan to monitor the ORDS download page once a day. We are relying on the URL for ORDS downloads to remain the same and for the positioning of release information to remain in the same place on the page. If any of that changes, the monitoring job definition will have to change.

The obvious first step is to sign up and create an account. Once logged in, create a monitoring job.

Enter the ORDS download page URL, press GO. When the image renders, position the monitoring box. This defines the section of the page that will be monitored for changes.
Give the job a name. The type is Visual Compare which is checked everyday for tiny changes.
When a change is identified you’ll receive an email like this one

There are alternative website monitoring software available to you. Some are free and some you have to install and run yourself. The above concept of tracking the ORDS download page for changes can be used in most.

Either way, following @OracleREST is always a good idea.

[Update for ORDS 22+] So what version of ORDS am I actually running?

A little while ago ORDS version 20.2.1.227.0350 became available for download from oracle.com and was also rolled out to Autonomous Database Shared regions around the world. Oracle REST Data Services has been available on Oracle Cloud for quite some time but this is the first time the release notes specifically mention Autonomous Database Shared.

If you are running a customer-managed ORDS for your Autonomous Transaction Processing, Autonomous Data Warehouse, or Autonomous JSON Database, you must upgrade your ORDS to version 20.2.1. Failure to do so could lead to service disruptions.

Oracle REST Data Services 20.2.1.r2270350 Release Note

Why does this matter? Before answering that, some basic concepts to cover first. At a high level, an ORDS installation consists of three parts:
– The Java application that accepts, validates and processes the HTTP requests. This is the ords.war running standalone, in WebLogic Server or Tomcat Server.
– The file system that contains the configuration settings for the ords.war to make and manage database connections, as well as static files and so on.
– The database that ORDS connects to and where users define & run their REST services in. This information and the database procedures for running these services are in the ORDS_METADATA schema.

When a customer first installs and configures ORDS the database(s) which ORDS will provide a RESTful interface for, will also have ORDS specific database objects installed. The version of the ords.war and the version of the ORDS_METADATA should be the same because they are coded, built and tested together before release. While it is possible that ORDS will work just fine if the ords.war and ORDS_METADATA versions are out of synch the expected behaviour is unspecified in this scenario.

At a high level, an ORDS installation consists of the application, file system and database.

ORDS Java application ( ords.war ) version

There are a number of ways to get the application version. From the command line…

java -jar ords.war version

Update for ORDS 22+

When using ORDS 22+ the above command will show you the version but will also show this error message:

java -jar ords.war version
Warning: Support for executing: java -jar ords.war has been deprecated.
Please add ords to your PATH and use the ords command instead.
...
ORDS: Release 22.4 Production 
...

The output does give you the high level version number but the correct command to use with ORDS 22+ is in fact:

ords --version

That’s assuming the ords script is in your path. If not, the correct command is:

$ORDS_INSTALL_DIR/bin/ords --version

Now, back to the other steps to get the ORDS Java Application version number…

From the SQL Developer Web through Preferences > About…

From within APEX through SQL Workshop > RESTful Services > ORDS RESTful Services…

From within APEX through Help (?) > About. In this case it’s called the APEX_LISTENER_VERSION…

ORDS repository ( ORDS_METADATA ) version

The metadata version can be obtained through an SQL query in the database…

select ords.installed_version from dual;

The above query can be executed in SQL Developer Web worksheet, APEX SQL Workshop > SQL Commands, sqlplus, sqlcl, SQL Developer. Effectively, anywhere you can run a query from.

Conclusion

It is possible for an ORDS installation to have the ords.war and ORDS_METADATA versions out of synch. This can happen if one runs a new version of ords.war against a database that already has ORDS installed without going through the installation or upgrade process or the database metadata is upgraded but the runtime ords.war is not. The latter can happen for Customer Managed ORDS on Autonomous Database. When doing so, always regularly check the application and database versions are in synch.

Match your METHOD with your SOURCE

Oracle REST Data Service makes it easy to provide a RESTful service interface to tables, views and procedures in your database. When two paradigms meet there’s rarely an exact one to one match between the concepts. It’s the same with Relational Database concepts and RESTful service concepts. These considerations must be kept in mind when when moving from one paradigm to another.

One thing to consider is the rule that most methods in a RESTful API must be idempotent. In the context of ORDS handlers this applies to GET, DELETE and PUT. However, ORDS does not enforce the rule because it can not be easily determined from a PL/SQL block if it is idempotent or not. To do so requires an understanding of not just the PL/SQL block itself, but also any functions or procedures it calls.

Idempotent considerations applies to the resource the endpoint represents. Therefore, if an audit entry must be written when a customer record is accessed, the PL/SQL that is defined in a GET handler can insert that audit record and still be considered idempotent because it is not modifying the customer record.

The ORDS handlers, particularly PL/SQL ones, that we have created should be reviewed from time to see if their declared method is correct. To get a list of your PL/SQL handlers that are not already defined with a POST method, run this query

SELECT
m.name,
m.uri_prefix,
t.uri_template,
h.source
FROM
user_ords_handlers h,
user_ords_templates t,
user_ords_modules m
WHERE
h.source_type = 'plsql/block'
AND h.method != 'POST'
AND h.template_id = t.id
AND t.module_id = m.id;

Review the results and ask does the source match the method?

Although there are more HTTP method types, these are the most common, and supported by Oracle REST Data Services.