Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Jdbc session map #8378

Merged
merged 19 commits into from
Jun 5, 2020
Merged

WIP: Jdbc session map #8378

merged 19 commits into from
Jun 5, 2020

Conversation

raju249
Copy link
Member

@raju249 raju249 commented Jun 1, 2020

Thanks for contributing to Selenium!
A PR well described will help maintainers to quickly review and merge it

Before submitting your PR, please check our contributing guidelines.
Avoid large PRs, help reviewers by making them as simple and short as possible.

Description

Fixes #8172

Motivation and Context

Types of changes

  • [-] Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • [-] Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • [-] I have read the contributing document.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

Copy link
Member

@shs96c shs96c left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Welcome aboard :) This is a great start. I've added some comments, but we're also chatting on Slack, so you can ask things there too.

// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.grid.sessionmap.config;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this to the jdbc package you've just created.

@@ -40,6 +43,18 @@ public SessionMapOptions(Config config) {
this.config = config;
}

public Connection getJdbcConnection() throws SQLException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this is only used in the JdbcSessionMap, it would be better to either move this into a helper method in the JdbcSessionMap, or create a jdbc.JdbcOptions class.

public class JdbcBackedSessionMap extends SessionMap implements Closeable {

private static final Json JSON = new Json();
private String tableName;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

final

try {
connection = sessionMapOptions.getJdbcConnection();
} catch (SQLException e) {
// TODO: Handle SQLException
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can throw a ConfigException here.

Statement insertStatement = connection.createStatement();
// TODO: Insert Call
} catch (SQLException e) {
// TODO: Handle SQLException
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can create a custom JdbcException which extends RuntimeException or WebDriverException if you want to.

try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Log using a java.util.logging.Logger at the warning level and swallow. There's nothing sensible to be done on the way out.

@raju249 raju249 force-pushed the jdbc-session-map branch 2 times, most recently from 30f1300 to 00854be Compare June 3, 2020 10:31
Copy link
Member

@shs96c shs96c left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great start. It's shaping up well.

Capabilities caps = null;

try {
ResultSet sessions = readSessionStatement(id).executeQuery();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use try-with-resources so that the ResultSet will be closed properly.

try {
ResultSet sessions = readSessionStatement(id).executeQuery();

while(sessions.next()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How many results are you expecting? Perhaps use a LIMIT 1 to ensure only one row at most is sent back?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the limit in place, you either have 0 or 1 columns. You should be able to replace the while with an if, which may simplify this a little, as you can fast-path out by throwing the NoSuchSessionException if there's no session found.

try {
uri = new URI(rawUri);
}
catch (URISyntaxException e) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Java style is to put the closing brace and the catch statement on the same line } catch (URISyntaxException e) {


sessions.close();

if (uri == null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those can't happen. If rawUri was null, then new URI would have thrown.

}

private PreparedStatement insertSessionStatement(Session session) throws SQLException {
PreparedStatement insertStatement = connection.prepareStatement("insert into " + tableName + " values(?,?)");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you know that tableName is not, itself, an SQL injection attack?

private String sessionUri(SessionId sessionId) {
Require.nonNull("Session ID", sessionId);

return "session:" + sessionId.toString() + ":uri";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The session id should be a UUID (it will be unique, even if it's not formatted correctly)


private PreparedStatement getDeleteSqlForSession(SessionId sessionId) throws SQLException{
PreparedStatement deleteSessionStatement = connection.prepareStatement("delete from " + tableName + " where " + sessionIdCol + " like ?");
deleteSessionStatement.setString(1, sessionUri(sessionId).concat("%"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A session id is unique and complete. You don't need to wildcard it.

@Parameter(
names = "--jdbc-sessionid-column",
description = "Column name where session id will be stored")
@ConfigValue(section = "sessions", name = "jdbc-sessionid-column", example = "session_id")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make life easy for yourself. Don't let this be configurable.

@Parameter(
names = "--jdbc-table",
description = "Name of the table in database to store sessions in.")
@ConfigValue(section = "sessions", name = "jdbc-table", example = "myP@ssw%d")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be very tempted to not make this configurable. If someone wants to use this, they'll just need to create the table needed.

names = "--jdbc-capabilities-column",
description = "Column name where session id will be stored")
@ConfigValue(section = "sessions", name = "jdbc-capabilities-column", example = "capabilities")
private String sessionCapsColumn;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, don't let this be configurable.

Copy link
Member

@shs96c shs96c left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a couple of nits, and we're there. This looks great :) Thank you!

try {
ResultSet sessions = readSessionStatement(id).executeQuery();

while(sessions.next()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the limit in place, you either have 0 or 1 columns. You should be able to replace the while with an if, which may simplify this a little, as you can fast-path out by throwing the NoSuchSessionException if there's no session found.

Tracer tracer = new LoggingOptions(config).getTracer();
JdbcSessionMapOptions sessionMapOptions = new JdbcSessionMapOptions(config);
String tableName = sessionMapOptions.getJdbcTableName();
String sessionIdColName = sessionMapOptions.getJdbcSessionIdColName();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of these should be hard-coded now, so there's no need to look them up in the options :)

private static final Logger LOG = Logger.getLogger(JdbcBackedSessionMap.class.getName());
private final Connection connection;
private final String tableName;
private final String sessionIdCol;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're hard-coding the table names, make these constants or just use magic strings....


private static final String SESSIONS_SECTION = "sessions";
private static final Logger LOG = Logger.getLogger(JdbcSessionMapOptions.class.getName());
private static final String tableName = "sessions_map";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's safe to move these constants into the JdbcSessionMap itself.

@raju249 raju249 force-pushed the jdbc-session-map branch from 1b90c77 to 516d60d Compare June 4, 2020 12:02
@raju249 raju249 force-pushed the jdbc-session-map branch from 516d60d to 01d6210 Compare June 5, 2020 04:51
Copy link
Member

@shs96c shs96c left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more round, then let's land this and iterate :)

@@ -73,6 +73,7 @@ java_export(
":base-command",
"//java/server/src/org/openqa/selenium/cli",
"//java/server/src/org/openqa/selenium/grid/config",
"//java/server/src/org/openqa/selenium/grid/sessionmap/jdbc",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this deliberate? Or a debugging aid?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After speaking on Slack, it seems that this is here to help bootstrap things so that new flags appear on the classpath.


private static final Json JSON = new Json();
private static final Logger LOG = Logger.getLogger(JdbcBackedSessionMap.class.getName());
private final EventBus bus;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: place the instance fields below the constants.

try {
connection = sessionMapOptions.getJdbcConnection();
} catch (SQLException e) {
throw new ConfigException(e.toString());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add the underlying exception too? new ConfigException(e)

String rawUri = null;

try (ResultSet sessions = readSessionStatement(id).executeQuery()){
if (sessions.next()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer multiple return points and less indenting:

if (!session.next()) {
  throw new NoSuchSessionException("Unable to find...");
}

}

private PreparedStatement insertSessionStatement(Session session) throws SQLException {
PreparedStatement insertStatement = connection.prepareStatement("insert into " + TABLE_NAME
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Newline before the arguments begin. That avoids excessive line length and makes the code a bit easier to read.

}

private PreparedStatement insertSessionStatement(Session session) throws SQLException {
PreparedStatement insertStatement = connection.prepareStatement("insert into " + TABLE_NAME
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using String.format instead of concatenating strings. When there's just one, it's fine, but it starts to get hard to read after a while.

}

private PreparedStatement readSessionStatement(SessionId sessionId) throws SQLException {
PreparedStatement getSessionsStatement = connection.prepareStatement("select * from " + TABLE_NAME
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: prefer String.format for readability

@Parameter(
names = "--jdbc-password",
description = "Password for the user to make a JDBC connection")
@ConfigValue(section = "sessions", name = "jdbc-password", example = "myP@ssw%d")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Humorous aside: We tend to use hunter2 as the password of choice: https://knowyourmeme.com/memes/hunter2 https://w3c.github.io/webdriver/#example-7

private final Config config;

public JdbcSessionMapOptions(Config config) {
this.config = config;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reuire.nonNull

Copy link
Member

@shs96c shs96c left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Congratulations on landing this. It's a lovely feature!

@@ -73,6 +73,7 @@ java_export(
":base-command",
"//java/server/src/org/openqa/selenium/cli",
"//java/server/src/org/openqa/selenium/grid/config",
"//java/server/src/org/openqa/selenium/grid/sessionmap/jdbc",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After speaking on Slack, it seems that this is here to help bootstrap things so that new flags appear on the classpath.

@shs96c shs96c merged commit f5fc6cd into SeleniumHQ:master Jun 5, 2020
@raju249 raju249 deleted the jdbc-session-map branch June 5, 2020 10:49
titusfortner pushed a commit to titusfortner/selenium that referenced this pull request Aug 13, 2020
Provides an implementation of the `SessionMap` that's
backed by JDBC.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Grid] SessionMap backed by JDBC
2 participants