Updated Branches: refs/heads/trunk 0b36afa62 -> 589107966
Reuse prepared statements in hot auth queries patch by Aleksey Yeschenko; reviewed by Jonathan Ellis for CASSANDRA-5594 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/1b5edee0 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/1b5edee0 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/1b5edee0 Branch: refs/heads/trunk Commit: 1b5edee00faeb03ec4fc5dff779a031f7093241b Parents: 234d4cf Author: Aleksey Yeschenko <[email protected]> Authored: Sun May 26 05:40:11 2013 +0300 Committer: Aleksey Yeschenko <[email protected]> Committed: Sun May 26 05:40:11 2013 +0300 ---------------------------------------------------------------------- CHANGES.txt | 1 + src/java/org/apache/cassandra/auth/Auth.java | 62 +++++++++----- .../apache/cassandra/auth/CassandraAuthorizer.java | 37 +++++++-- .../cassandra/auth/PasswordAuthenticator.java | 40 +++++++--- 4 files changed, 100 insertions(+), 40 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/1b5edee0/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index d3973b6..7b1970c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -7,6 +7,7 @@ * Improve batchlog replay behavior and hint ttl handling (CASSANDRA-5314) * Exclude localTimestamp from validation for tombstones (CASSANDRA-5398) * cqlsh: add custom prompt support (CASSANDRA-5539) + * Reuse prepared statements in hot auth queries (CASSANDRA-5594) Merged from 1.1: * Remove buggy thrift max message length option (CASSANDRA-5529) * Fix NPE in Pig's widerow mode (CASSANDRA-5488) http://git-wip-us.apache.org/repos/asf/cassandra/blob/1b5edee0/src/java/org/apache/cassandra/auth/Auth.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/Auth.java b/src/java/org/apache/cassandra/auth/Auth.java index 7ec5a33..c561aab 100644 --- a/src/java/org/apache/cassandra/auth/Auth.java +++ b/src/java/org/apache/cassandra/auth/Auth.java @@ -20,6 +20,7 @@ package org.apache.cassandra.auth; import java.util.concurrent.TimeUnit; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,12 +30,14 @@ import org.apache.cassandra.config.KSMetaData; import org.apache.cassandra.config.Schema; import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.cql3.QueryProcessor; +import org.apache.cassandra.cql3.statements.SelectStatement; import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.exceptions.RequestExecutionException; +import org.apache.cassandra.exceptions.RequestValidationException; import org.apache.cassandra.locator.SimpleStrategy; -import org.apache.cassandra.service.IMigrationListener; -import org.apache.cassandra.service.MigrationManager; -import org.apache.cassandra.service.StorageService; +import org.apache.cassandra.service.*; +import org.apache.cassandra.transport.messages.ResultMessage; +import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; public class Auth @@ -57,6 +60,8 @@ public class Auth USERS_CF, 90 * 24 * 60 * 60); // 3 months. + private static SelectStatement selectUserStatement; + /** * Checks if the username is stored in AUTH_KS.USERS_CF. * @@ -65,15 +70,7 @@ public class Auth */ public static boolean isExistingUser(String username) { - String query = String.format("SELECT * FROM %s.%s WHERE name = '%s'", AUTH_KS, USERS_CF, escape(username)); - try - { - return !QueryProcessor.process(query, consistencyForUser(username)).isEmpty(); - } - catch (RequestExecutionException e) - { - throw new RuntimeException(e); - } + return !selectUser(username).isEmpty(); } /** @@ -84,16 +81,8 @@ public class Auth */ public static boolean isSuperuser(String username) { - String query = String.format("SELECT super FROM %s.%s WHERE name = '%s'", AUTH_KS, USERS_CF, escape(username)); - try - { - UntypedResultSet result = QueryProcessor.process(query, consistencyForUser(username)); - return !result.isEmpty() && result.one().getBoolean("super"); - } - catch (RequestExecutionException e) - { - throw new RuntimeException(e); - } + UntypedResultSet result = selectUser(username); + return !result.isEmpty() && result.one().getBoolean("super"); } /** @@ -157,6 +146,16 @@ public class Auth SUPERUSER_SETUP_DELAY, TimeUnit.MILLISECONDS); } + + try + { + String query = String.format("SELECT * FROM %s.%s WHERE name = ?", AUTH_KS, USERS_CF); + selectUserStatement = (SelectStatement) QueryProcessor.parseStatement(query).prepare().statement; + } + catch (RequestValidationException e) + { + throw new AssertionError(e); // not supposed to happen + } } // Only use QUORUM cl for the default superuser. @@ -227,6 +226,25 @@ public class Auth return StringUtils.replace(name, "'", "''"); } + private static UntypedResultSet selectUser(String username) + { + try + { + ResultMessage.Rows rows = selectUserStatement.execute(consistencyForUser(username), + new QueryState(new ClientState(true)), + Lists.newArrayList(ByteBufferUtil.bytes(username))); + return new UntypedResultSet(rows.result); + } + catch (RequestValidationException e) + { + throw new AssertionError(e); // not supposed to happen + } + catch (RequestExecutionException e) + { + throw new RuntimeException(e); + } + } + /** * IMigrationListener implementation that cleans up permissions on dropped resources. */ http://git-wip-us.apache.org/repos/asf/cassandra/blob/1b5edee0/src/java/org/apache/cassandra/auth/CassandraAuthorizer.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/CassandraAuthorizer.java b/src/java/org/apache/cassandra/auth/CassandraAuthorizer.java index 0518734..396be71 100644 --- a/src/java/org/apache/cassandra/auth/CassandraAuthorizer.java +++ b/src/java/org/apache/cassandra/auth/CassandraAuthorizer.java @@ -28,9 +28,14 @@ import org.slf4j.LoggerFactory; import org.apache.cassandra.config.Schema; import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.cql3.QueryProcessor; +import org.apache.cassandra.cql3.statements.SelectStatement; import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.exceptions.*; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.messages.ResultMessage; +import org.apache.cassandra.utils.ByteBufferUtil; /** * CassandraAuthorizer is an IAuthorizer implementation that keeps @@ -55,20 +60,26 @@ public class CassandraAuthorizer implements IAuthorizer PERMISSIONS_CF, 90 * 24 * 60 * 60); // 3 months. + private SelectStatement authorizeStatement; + // Returns every permission on the resource granted to the user. public Set<Permission> authorize(AuthenticatedUser user, IResource resource) { if (user.isSuper()) return Permission.ALL; - UntypedResultSet rows; + UntypedResultSet result; try { - rows = process(String.format("SELECT permissions FROM %s.%s WHERE username = '%s' AND resource = '%s'", - Auth.AUTH_KS, - PERMISSIONS_CF, - escape(user.getName()), - escape(resource.getName()))); + ResultMessage.Rows rows = authorizeStatement.execute(ConsistencyLevel.ONE, + new QueryState(new ClientState(true)), + Lists.newArrayList(ByteBufferUtil.bytes(user.getName()), + ByteBufferUtil.bytes(resource.getName()))); + result = new UntypedResultSet(rows.result); + } + catch (RequestValidationException e) + { + throw new AssertionError(e); // not supposed to happen } catch (RequestExecutionException e) { @@ -76,11 +87,11 @@ public class CassandraAuthorizer implements IAuthorizer return Permission.NONE; } - if (rows.isEmpty() || !rows.one().has(PERMISSIONS)) + if (result.isEmpty() || !result.one().has(PERMISSIONS)) return Permission.NONE; Set<Permission> permissions = EnumSet.noneOf(Permission.class); - for (String perm : rows.one().getSet(PERMISSIONS, UTF8Type.instance)) + for (String perm : result.one().getSet(PERMISSIONS, UTF8Type.instance)) permissions.add(Permission.valueOf(perm)); return permissions; } @@ -239,6 +250,16 @@ public class CassandraAuthorizer implements IAuthorizer throw new AssertionError(e); } } + + try + { + String query = String.format("SELECT permissions FROM %s.%s WHERE username = ? AND resource = ?", Auth.AUTH_KS, PERMISSIONS_CF); + authorizeStatement = (SelectStatement) QueryProcessor.parseStatement(query).prepare().statement; + } + catch (RequestValidationException e) + { + throw new AssertionError(e); // not supposed to happen + } } // We only worry about one character ('). Make sure it's properly escaped. http://git-wip-us.apache.org/repos/asf/cassandra/blob/1b5edee0/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java b/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java index 01adb1b..bcbdd29 100644 --- a/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java +++ b/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java @@ -22,6 +22,7 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,12 +31,14 @@ import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.Schema; import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.cql3.QueryProcessor; +import org.apache.cassandra.cql3.statements.SelectStatement; import org.apache.cassandra.db.ConsistencyLevel; -import org.apache.cassandra.exceptions.AuthenticationException; -import org.apache.cassandra.exceptions.ConfigurationException; -import org.apache.cassandra.exceptions.InvalidRequestException; -import org.apache.cassandra.exceptions.RequestExecutionException; +import org.apache.cassandra.exceptions.*; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.service.QueryState; import org.apache.cassandra.service.StorageService; +import org.apache.cassandra.transport.messages.ResultMessage; +import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; import org.mindrot.jbcrypt.BCrypt; @@ -68,6 +71,8 @@ public class PasswordAuthenticator implements IAuthenticator CREDENTIALS_CF, 90 * 24 * 60 * 60); // 3 months. + private SelectStatement authenticateStatement; + // No anonymous access. public boolean requireAuthentication() { @@ -98,12 +103,14 @@ public class PasswordAuthenticator implements IAuthenticator UntypedResultSet result; try { - result = process(String.format("SELECT %s FROM %s.%s WHERE username = '%s'", - SALTED_HASH, - Auth.AUTH_KS, - CREDENTIALS_CF, - escape(username)), - consistencyForUser(username)); + ResultMessage.Rows rows = authenticateStatement.execute(consistencyForUser(username), + new QueryState(new ClientState(true)), + Lists.newArrayList(ByteBufferUtil.bytes(username))); + result = new UntypedResultSet(rows.result); + } + catch (RequestValidationException e) + { + throw new AssertionError(e); // not supposed to happen } catch (RequestExecutionException e) { @@ -174,6 +181,19 @@ public class PasswordAuthenticator implements IAuthenticator Auth.SUPERUSER_SETUP_DELAY, TimeUnit.MILLISECONDS); } + + try + { + String query = String.format("SELECT %s FROM %s.%s WHERE username = ?", + SALTED_HASH, + Auth.AUTH_KS, + CREDENTIALS_CF); + authenticateStatement = (SelectStatement) QueryProcessor.parseStatement(query).prepare().statement; + } + catch (RequestValidationException e) + { + throw new AssertionError(e); // not supposed to happen + } } private void setupCredentialsTable()
