KEYCLOAK-2737 connectionsMongo: Support for 'databaseSchema: validate'

This commit is contained in:
mposolda 2016-04-04 22:22:41 +02:00
parent e6df30602e
commit 3a8b450575
6 changed files with 105 additions and 34 deletions

View file

@ -186,8 +186,9 @@ bin/add-user-keycloak.[sh|bat] -r master -u <username> -p <password>
<term>databaseSchema</term> <term>databaseSchema</term>
<listitem> <listitem>
<para> <para>
Specify if schema should be updated or validated. Valid values are "update" and "validate". Value "update is default and means that DB schema will be updated to latest version. Specify if schema should be updated or validated. Valid values are <literal>update</literal> and <literal>validate</literal>.
Value "validate" won't touch database schema, but it will fail to start if schema is not updated to latest version. Value <literal>update</literal> is default and means that DB schema will be updated to latest version.
Value <literal>validate</literal> won't touch database schema, but it will fail to start if schema is not updated to latest version.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
@ -304,7 +305,11 @@ bin/add-user-keycloak.[sh|bat] -r master -u <username> -p <password>
}, },
"user": { "user": {
"provider": "${keycloak.user.provider:jpa}" "provider": "jpa"
},
"userSessionPersister": {
"provider": "jpa"
}, },
]]></programlisting> ]]></programlisting>
@ -325,6 +330,10 @@ bin/add-user-keycloak.[sh|bat] -r master -u <username> -p <password>
"user": { "user": {
"provider": "mongo" "provider": "mongo"
}, },
"userSessionPersister": {
"provider": "mongo"
},
]]></programlisting> ]]></programlisting>
And at the end of the file add the snippet like this where you can configure details about your Mongo database: And at the end of the file add the snippet like this where you can configure details about your Mongo database:
@ -334,7 +343,8 @@ bin/add-user-keycloak.[sh|bat] -r master -u <username> -p <password>
"host": "127.0.0.1", "host": "127.0.0.1",
"port": "27017", "port": "27017",
"db": "keycloak", "db": "keycloak",
"connectionsPerHost": 100 "connectionsPerHost": 100,
"databaseSchema": "update"
} }
} }
]]></programlisting> ]]></programlisting>
@ -344,6 +354,14 @@ bin/add-user-keycloak.[sh|bat] -r master -u <username> -p <password>
if you want authenticate against your MongoDB. If user and password are not specified, Keycloak will connect if you want authenticate against your MongoDB. If user and password are not specified, Keycloak will connect
unauthenticated to your MongoDB. unauthenticated to your MongoDB.
</para> </para>
<para>
Option <literal>databaseSchema</literal> specify if mongo database "schema" should be updated or validated. Valid values are <literal>update</literal> and <literal>validate</literal>.
Value <literal>update</literal> means that DB schema will be updated to latest version.
Value <literal>validate</literal> won't touch database schema, but it will fail to start if schema is not updated to latest version.
</para>
<note>
Mongo doesn't have real database schema, but 'schema' in this case means to which Keycloak version the data in the Mongo database corresponds.
</note>
<para>Finally there is set of optional configuration options, which can be used to specify connection-pooling capabilities of Mongo client. Supported int options are: <para>Finally there is set of optional configuration options, which can be used to specify connection-pooling capabilities of Mongo client. Supported int options are:
<literal>connectionsPerHost</literal>, <literal>threadsAllowedToBlockForConnectionMultiplier</literal>, <literal>maxWaitTime</literal>, <literal>connectTimeout</literal> <literal>connectionsPerHost</literal>, <literal>threadsAllowedToBlockForConnectionMultiplier</literal>, <literal>maxWaitTime</literal>, <literal>connectTimeout</literal>
<literal>socketTimeout</literal>. Supported boolean options are: <literal>socketKeepAlive</literal>, <literal>autoConnectRetry</literal>. <literal>socketTimeout</literal>. Supported boolean options are: <literal>socketKeepAlive</literal>, <literal>autoConnectRetry</literal>.
@ -360,7 +378,8 @@ bin/add-user-keycloak.[sh|bat] -r master -u <username> -p <password>
"connectionsMongo": { "connectionsMongo": {
"default": { "default": {
"uri": "mongodb://user:password@127.0.0.1/authentication", "uri": "mongodb://user:password@127.0.0.1/authentication",
"db": "keycloak" "db": "keycloak",
"databaseSchema": "update"
} }
} }
]]></programlisting> ]]></programlisting>

View file

@ -156,13 +156,24 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
} }
private void update(KeycloakSession session) { private void update(KeycloakSession session) {
MongoUpdaterProvider mongoUpdater = session.getProvider(MongoUpdaterProvider.class); String databaseSchema = config.get("databaseSchema");
if (mongoUpdater == null) { if (databaseSchema == null) {
throw new RuntimeException("Can't update database: Mongo updater provider not found"); throw new RuntimeException("Property 'databaseSchema' needs to be specified in the configuration of mongo connections");
} else {
MongoUpdaterProvider mongoUpdater = session.getProvider(MongoUpdaterProvider.class);
if (mongoUpdater == null) {
throw new RuntimeException("Can't update database: Mongo updater provider not found");
}
if (databaseSchema.equals("update")) {
mongoUpdater.update(session, db);
} else if (databaseSchema.equals("validate")) {
mongoUpdater.validate(session, db);
} else {
throw new RuntimeException("Invalid value for databaseSchema: " + databaseSchema);
}
} }
mongoUpdater.update(session, db);
} }

View file

@ -26,6 +26,8 @@ import org.keycloak.provider.Provider;
*/ */
public interface MongoUpdaterProvider extends Provider { public interface MongoUpdaterProvider extends Provider {
public void update(KeycloakSession session, DB db); void update(KeycloakSession session, DB db);
void validate(KeycloakSession session, DB db);
} }

View file

@ -65,38 +65,19 @@ public class DefaultMongoUpdaterProvider implements MongoUpdaterProvider {
log.debug("Starting database update"); log.debug("Starting database update");
try { try {
boolean changeLogExists = db.collectionExists(CHANGE_LOG_COLLECTION); boolean changeLogExists = db.collectionExists(CHANGE_LOG_COLLECTION);
boolean realmExists = db.collectionExists("realms");
DBCollection changeLog = db.getCollection(CHANGE_LOG_COLLECTION); DBCollection changeLog = db.getCollection(CHANGE_LOG_COLLECTION);
List<String> executed = new LinkedList<String>(); List<String> executed = getExecuted(db, changeLogExists, changeLog);
if (!changeLogExists && realmExists) { List<Update> updatesToRun = getUpdatesToRun(executed);
Update1_0_0_Final u = new Update1_0_0_Final();
executed.add(u.getId());
createLog(changeLog, u, 1);
} else if (changeLogExists) {
DBCursor cursor = changeLog.find().sort(new BasicDBObject("orderExecuted", 1));
while (cursor.hasNext()) {
executed.add((String) cursor.next().get("_id"));
}
}
List<Update> updatesToRun = new LinkedList<Update>();
for (Class<? extends Update> updateClass : updates) {
Update u = updateClass.newInstance();
if (!executed.contains(u.getId())) {
updatesToRun.add(u);
}
}
if (!updatesToRun.isEmpty()) { if (!updatesToRun.isEmpty()) {
if (executed.isEmpty()) { if (executed.isEmpty()) {
log.info("Initializing database schema"); log.info("Initializing database schema");
} else { } else {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.infov("Updating database from {0} to {1}", executed.get(executed.size() - 1), updatesToRun.get(updatesToRun.size() - 1).getId()); log.debugv("Updating database from {0} to {1}", executed.get(executed.size() - 1), updatesToRun.get(updatesToRun.size() - 1).getId());
} else { } else {
log.debugv("Updating database"); log.info("Updating database");
} }
} }
@ -121,10 +102,66 @@ public class DefaultMongoUpdaterProvider implements MongoUpdaterProvider {
} }
} }
@Override
public void validate(KeycloakSession session, DB db) {
log.debug("Validating database");
boolean changeLogExists = db.collectionExists(CHANGE_LOG_COLLECTION);
DBCollection changeLog = db.getCollection(CHANGE_LOG_COLLECTION);
List<String> executed = getExecuted(db, changeLogExists, changeLog);
List<Update> updatesToRun = getUpdatesToRun(executed);
if (!updatesToRun.isEmpty()) {
String errorMessage = String.format("Failed to validate Mongo database schema. Schema needs updating database from %s to %s. Please change databaseSchema to 'update'",
executed.get(executed.size() - 1), updatesToRun.get(updatesToRun.size() - 1).getId());
throw new RuntimeException(errorMessage);
} else {
log.debug("Validation passed. Database is up to date");
}
}
private List<String> getExecuted(DB db, boolean changeLogExists, DBCollection changeLog) {
boolean realmExists = db.collectionExists("realms");
List<String> executed = new LinkedList<>();
if (!changeLogExists && realmExists) {
Update1_0_0_Final u = new Update1_0_0_Final();
executed.add(u.getId());
createLog(changeLog, u, 1);
} else if (changeLogExists) {
DBCursor cursor = changeLog.find().sort(new BasicDBObject("orderExecuted", 1));
while (cursor.hasNext()) {
executed.add((String) cursor.next().get("_id"));
}
}
return executed;
}
private List<Update> getUpdatesToRun(List<String> executed) {
try {
List<Update> updatesToRun = new LinkedList<>();
for (Class<? extends Update> updateClass : updates) {
Update u = updateClass.newInstance();
if (!executed.contains(u.getId())) {
updatesToRun.add(u);
}
}
return updatesToRun;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void createLog(DBCollection changeLog, Update update, int orderExecuted) { private void createLog(DBCollection changeLog, Update update, int orderExecuted) {
changeLog.insert(new BasicDBObject("_id", update.getId()).append("dateExecuted", new Date()).append("orderExecuted", orderExecuted)); changeLog.insert(new BasicDBObject("_id", update.getId()).append("dateExecuted", new Date()).append("orderExecuted", orderExecuted));
} }
@Override @Override
public void close() { public void close() {
} }

View file

@ -94,6 +94,7 @@
"host": "${keycloak.connectionsMongo.host:127.0.0.1}", "host": "${keycloak.connectionsMongo.host:127.0.0.1}",
"port": "${keycloak.connectionsMongo.port:27017}", "port": "${keycloak.connectionsMongo.port:27017}",
"db": "${keycloak.connectionsMongo.db:keycloak}", "db": "${keycloak.connectionsMongo.db:keycloak}",
"databaseSchema": "${keycloak.connectionsMongo.databaseSchema:update}",
"connectionsPerHost": "${keycloak.connectionsMongo.connectionsPerHost:100}" "connectionsPerHost": "${keycloak.connectionsMongo.connectionsPerHost:100}"
} }
}, },

View file

@ -73,6 +73,7 @@
"host": "${keycloak.connectionsMongo.host:127.0.0.1}", "host": "${keycloak.connectionsMongo.host:127.0.0.1}",
"port": "${keycloak.connectionsMongo.port:27017}", "port": "${keycloak.connectionsMongo.port:27017}",
"db": "${keycloak.connectionsMongo.db:keycloak}", "db": "${keycloak.connectionsMongo.db:keycloak}",
"databaseSchema": "${keycloak.connectionsMongo.databaseSchema:update}",
"connectionsPerHost": "${keycloak.connectionsMongo.connectionsPerHost:100}" "connectionsPerHost": "${keycloak.connectionsMongo.connectionsPerHost:100}"
} }
}, },