KEYCLOAK-9947 KEYCLOAK-10451 Better support for DB manual migration test with DB provided by docker or dballocator plugin
This commit is contained in:
parent
1778269c68
commit
a46bf708c0
5 changed files with 179 additions and 49 deletions
|
@ -312,44 +312,13 @@ The profile "test-7X-migration" indicates from which version you want to test mi
|
|||
|
||||
Same test as above, but it uses manual migration mode. During startup of the new Keycloak server, Liquibase won't automatically perform DB update, but it
|
||||
just exports the needed SQL into the script. This SQL script then needs to be manually executed against the DB.
|
||||
Then there is another startup of the new Keycloak server against the DB, which already has SQL changes applied and
|
||||
the same test as in `auto` mode (MigrationTest) is executed to test that data are correct.
|
||||
|
||||
1) Prepare MySQL DB (Same as above)
|
||||
The test is executed in same way as the "auto" DB migration test with the only difference
|
||||
that you need to use property `migration.mode` with the value `manual` .
|
||||
|
||||
2) Run the test (Update according to your DB connection, versions etc). This step will end with failure, but that's expected:
|
||||
|
||||
mvn -f testsuite/integration-arquillian/pom.xml \
|
||||
clean install \
|
||||
-Pauth-server-wildfly,jpa,clean-jpa,auth-server-migration,test-70-migration \
|
||||
-Dtest=MigrationTest \
|
||||
-Dmigration.mode=manual \
|
||||
-Djdbc.mvn.groupId=mysql \
|
||||
-Djdbc.mvn.artifactId=mysql-connector-java \
|
||||
-Djdbc.mvn.version=8.0.12 \
|
||||
-Djdbc.mvn.version.legacy=5.1.38 \
|
||||
-Dkeycloak.connectionsJpa.url=jdbc:mysql://$DB_HOST/keycloak \
|
||||
-Dkeycloak.connectionsJpa.user=keycloak \
|
||||
-Dkeycloak.connectionsJpa.password=keycloak
|
||||
|
||||
3) Manually execute the SQL script against your DB. With Mysql, you can use this command (KEYCLOAK_SRC points to the directory with the Keycloak codebase):
|
||||
|
||||
mysql -h $DB_HOST -u keycloak -pkeycloak < $KEYCLOAK_SRC/testsuite/integration-arquillian/tests/base/target/containers/auth-server-wildfly/keycloak-database-update.sql
|
||||
|
||||
4) Finally run the migration test, which will verify that DB migration was successful. This should end with success:
|
||||
|
||||
mvn -f testsuite/integration-arquillian/tests/base/pom.xml \
|
||||
clean install \
|
||||
-Pauth-server-wildfly,test-70-migration \
|
||||
-Dskip.add.user.json=true \
|
||||
-Dtest=MigrationTest
|
||||
|
||||
### JSON export/import migration test
|
||||
This will start latest Keycloak and import the realm JSON file, which was previously exported from Keycloak 1.9.8.Final
|
||||
|
||||
|
||||
mvn -f testsuite/integration-arquillian/pom.xml \
|
||||
clean install \
|
||||
-Pauth-server-wildfly \
|
||||
-Dtest=JsonFileImport*MigrationTest
|
||||
-Dmigration.mode=manual
|
||||
|
||||
|
||||
## Server configuration migration test
|
||||
|
|
|
@ -89,11 +89,6 @@
|
|||
<jdbc.mvn.artifactId>h2</jdbc.mvn.artifactId>
|
||||
<jdbc.mvn.version>${h2.version}</jdbc.mvn.version>
|
||||
|
||||
<keycloak.connectionsJpa.driver>org.h2.Driver</keycloak.connectionsJpa.driver>
|
||||
<keycloak.connectionsJpa.database>keycloak</keycloak.connectionsJpa.database>
|
||||
<keycloak.connectionsJpa.user>sa</keycloak.connectionsJpa.user>
|
||||
<keycloak.connectionsJpa.password></keycloak.connectionsJpa.password>
|
||||
<keycloak.connectionsJpa.url>jdbc:h2:mem:test;MVCC=TRUE;DB_CLOSE_DELAY=-1</keycloak.connectionsJpa.url>
|
||||
<keycloak.connectionsJpa.schema>DEFAULT</keycloak.connectionsJpa.schema>
|
||||
|
||||
<dballocator.skip>true</dballocator.skip>
|
||||
|
@ -393,6 +388,23 @@
|
|||
</pluginManagement>
|
||||
</build>
|
||||
</profile>
|
||||
<!-- Specifies default DB properties, which are used for test with embedded H2. Those are overriden when testing with any docker container
|
||||
profile like "db-mysql" or any dballocator profile. Also those can be manually overriden when running the test from CMD -->
|
||||
<profile>
|
||||
<id>db-default-properties</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>!dballocator.uri</name>
|
||||
</property>
|
||||
</activation>
|
||||
<properties>
|
||||
<keycloak.connectionsJpa.driver>org.h2.Driver</keycloak.connectionsJpa.driver>
|
||||
<keycloak.connectionsJpa.database>keycloak</keycloak.connectionsJpa.database>
|
||||
<keycloak.connectionsJpa.user>sa</keycloak.connectionsJpa.user>
|
||||
<keycloak.connectionsJpa.password></keycloak.connectionsJpa.password>
|
||||
<keycloak.connectionsJpa.url>jdbc:h2:mem:test;MVCC=TRUE;DB_CLOSE_DELAY=-1</keycloak.connectionsJpa.url>
|
||||
</properties>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>db-mysql</id>
|
||||
<properties>
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.keycloak.testsuite.arquillian;
|
|||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.jboss.arquillian.container.spi.ContainerRegistry;
|
||||
import org.jboss.arquillian.container.spi.client.container.LifecycleException;
|
||||
import org.jboss.arquillian.container.spi.event.StartContainer;
|
||||
import org.jboss.arquillian.container.spi.event.StartSuiteContainers;
|
||||
import org.jboss.arquillian.container.spi.event.StopContainer;
|
||||
|
@ -35,28 +36,28 @@ import org.jboss.arquillian.test.spi.event.suite.BeforeClass;
|
|||
import org.jboss.arquillian.test.spi.event.suite.BeforeSuite;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.admin.client.Keycloak;
|
||||
import org.keycloak.common.util.StringPropertyReplacer;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.services.error.KeycloakErrorHandler;
|
||||
import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected;
|
||||
import org.keycloak.testsuite.client.KeycloakTestingClient;
|
||||
import org.keycloak.testsuite.util.LogChecker;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.testsuite.util.SqlUtils;
|
||||
import org.keycloak.testsuite.util.SystemInfoHelper;
|
||||
import org.wildfly.extras.creaper.commands.undertow.AddUndertowListener;
|
||||
import org.wildfly.extras.creaper.commands.undertow.RemoveUndertowListener;
|
||||
import org.wildfly.extras.creaper.commands.undertow.SslVerifyClient;
|
||||
import org.wildfly.extras.creaper.commands.undertow.UndertowListenerType;
|
||||
import org.wildfly.extras.creaper.core.CommandFailedException;
|
||||
import org.keycloak.testsuite.util.TextFileChecker;
|
||||
import org.wildfly.extras.creaper.core.ManagementClient;
|
||||
import org.wildfly.extras.creaper.core.online.CliException;
|
||||
import org.wildfly.extras.creaper.core.online.OnlineManagementClient;
|
||||
import org.wildfly.extras.creaper.core.online.OnlineOptions;
|
||||
import org.wildfly.extras.creaper.core.online.operations.Address;
|
||||
import org.wildfly.extras.creaper.core.online.operations.OperationException;
|
||||
import org.wildfly.extras.creaper.core.online.operations.Operations;
|
||||
import org.wildfly.extras.creaper.core.online.operations.admin.Administration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
@ -65,7 +66,6 @@ import java.util.Objects;
|
|||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -113,12 +113,17 @@ public class AuthServerTestEnricher {
|
|||
public static final String AUTH_SERVER_CROSS_DC_PROPERTY = "auth.server.crossdc";
|
||||
public static final boolean AUTH_SERVER_CROSS_DC = Boolean.parseBoolean(System.getProperty(AUTH_SERVER_CROSS_DC_PROPERTY, "false"));
|
||||
|
||||
public static final String AUTH_SERVER_HOME_PROPERTY = "auth.server.home";
|
||||
|
||||
public static final String CACHE_SERVER_LIFECYCLE_SKIP_PROPERTY = "cache.server.lifecycle.skip";
|
||||
public static final boolean CACHE_SERVER_LIFECYCLE_SKIP = Boolean.parseBoolean(System.getProperty(CACHE_SERVER_LIFECYCLE_SKIP_PROPERTY, "false"));
|
||||
|
||||
|
||||
public static final Boolean START_MIGRATION_CONTAINER = "auto".equals(System.getProperty("migration.mode")) ||
|
||||
"manual".equals(System.getProperty("migration.mode"));
|
||||
private static final String MIGRATION_MODE_PROPERTY = "migration.mode";
|
||||
private static final String MIGRATION_MODE_AUTO = "auto";
|
||||
private static final String MIGRATION_MODE_MANUAL = "manual";
|
||||
public static final Boolean START_MIGRATION_CONTAINER = MIGRATION_MODE_AUTO.equals(System.getProperty(MIGRATION_MODE_PROPERTY)) ||
|
||||
MIGRATION_MODE_MANUAL.equals(System.getProperty(MIGRATION_MODE_PROPERTY));
|
||||
|
||||
@Inject
|
||||
@SuiteScoped
|
||||
|
@ -333,9 +338,60 @@ public class AuthServerTestEnricher {
|
|||
public void startAuthContainer(@Observes(precedence = 0) StartSuiteContainers event) {
|
||||
//frontend-only (either load-balancer or auth-server)
|
||||
log.debug("Starting auth server before suite");
|
||||
|
||||
try {
|
||||
startContainerEvent.fire(new StartContainer(suiteContext.getAuthServerInfo().getArquillianContainer()));
|
||||
} catch (Exception e) {
|
||||
// It is expected that server startup fails with migration-mode-manual
|
||||
if (e instanceof LifecycleException && handleManualMigration()) {
|
||||
log.info("Starting server again after manual DB migration was finished");
|
||||
startContainerEvent.fire(new StartContainer(suiteContext.getAuthServerInfo().getArquillianContainer()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Just re-throw the exception
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if we are in manual DB migration test and if the previously created SQL script was successfully executed.
|
||||
* Returns false if we are not in manual DB migration test or SQL script couldn't be executed for any reason.
|
||||
* @return see method description
|
||||
*/
|
||||
private boolean handleManualMigration() {
|
||||
// It is expected that server startup fails with migration-mode-manual
|
||||
if (!MIGRATION_MODE_MANUAL.equals(System.getProperty(MIGRATION_MODE_PROPERTY))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String authServerHome = System.getProperty(AUTH_SERVER_HOME_PROPERTY);
|
||||
if (authServerHome == null) {
|
||||
log.warnf("Property '%s' was missing during manual mode migration test", AUTH_SERVER_HOME_PROPERTY);
|
||||
return false;
|
||||
}
|
||||
|
||||
String sqlScriptPath = authServerHome + File.separator + "keycloak-database-update.sql";
|
||||
if (!new File(sqlScriptPath).exists()) {
|
||||
log.warnf("File '%s' didn't exists during manual mode migration test", sqlScriptPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Run manual migration with the ant task
|
||||
log.infof("Running SQL script created by liquibase during manual migration flow", sqlScriptPath);
|
||||
String prefix = "keycloak.connectionsJpa.";
|
||||
String jdbcDriver = System.getProperty(prefix + "driver");
|
||||
String dbUrl = StringPropertyReplacer.replaceProperties(System.getProperty(prefix + "url"));
|
||||
String dbUser = System.getProperty(prefix + "user");
|
||||
String dbPassword = System.getProperty(prefix + "password");
|
||||
|
||||
SqlUtils.runSqlScript(sqlScriptPath, jdbcDriver, dbUrl, dbUser, dbPassword);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private static final Pattern RECOGNIZED_ERRORS = Pattern.compile("ERROR|SEVERE|Exception ");
|
||||
private static final Pattern IGNORED = Pattern.compile("Jetty ALPN support not found|org.keycloak.events");
|
||||
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright 2017 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.tools.ant.Project;
|
||||
import org.apache.tools.ant.taskdefs.SQLExec;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class SqlUtils {
|
||||
|
||||
protected static final Logger log = Logger.getLogger(SqlUtils.class);
|
||||
|
||||
|
||||
/**
|
||||
* Run given SQL Script against specified DB
|
||||
*
|
||||
* @param sqlFilePath absolute path to the SQL file
|
||||
* @param jdbcDriverClass must be on the classpath
|
||||
* @param dbUrl
|
||||
* @param dbUsername
|
||||
* @param dbPassword
|
||||
*/
|
||||
public static void runSqlScript(String sqlFilePath, String jdbcDriverClass,
|
||||
String dbUrl, String dbUsername, String dbPassword) {
|
||||
log.infof("Running SQL script from file '%s'\n jdbcDriverClass=%s\n dbUrl=%s\n dbUsername=%s\n dbPassword=%s\n",
|
||||
sqlFilePath, jdbcDriverClass, dbUrl, dbUsername, dbPassword);
|
||||
|
||||
final class SqlExecuter extends SQLExec {
|
||||
public SqlExecuter() {
|
||||
Project project = new Project();
|
||||
project.init();
|
||||
setProject(project);
|
||||
setTaskType("sql");
|
||||
setTaskName("sql");
|
||||
}
|
||||
}
|
||||
|
||||
SqlExecuter executer = new SqlExecuter();
|
||||
executer.setSrc(new File(sqlFilePath));
|
||||
executer.setDriver(jdbcDriverClass);
|
||||
executer.setPassword(dbPassword);
|
||||
executer.setUserid(dbUsername);
|
||||
executer.setUrl(dbUrl);
|
||||
|
||||
if (dbUrl.contains("mssql") || jdbcDriverClass.contains("mssql")) {
|
||||
log.info("Using alternative delimiter due the MSSQL");
|
||||
executer.setDelimiter("go");
|
||||
SQLExec.DelimiterType dt = new SQLExec.DelimiterType();
|
||||
dt.setValue(SQLExec.DelimiterType.ROW);
|
||||
executer.setDelimiterType(dt);
|
||||
}
|
||||
|
||||
// See KEYCLOAK-3876
|
||||
if (dbUrl.contains("oracle") || jdbcDriverClass.contains("oracle")) {
|
||||
log.info("Removing 'SET DEFINE OFF' from the SQL script due the Oracle");
|
||||
try {
|
||||
String content = IOUtils.toString(new FileInputStream(sqlFilePath), StandardCharsets.UTF_8);
|
||||
content = content.replaceAll("SET DEFINE OFF;", "");
|
||||
IOUtils.write(content, new FileOutputStream(sqlFilePath), StandardCharsets.UTF_8);
|
||||
} catch (IOException fnfe) {
|
||||
throw new RuntimeException(fnfe);
|
||||
}
|
||||
}
|
||||
|
||||
executer.execute();
|
||||
}
|
||||
}
|
|
@ -49,6 +49,7 @@ import org.keycloak.representations.idm.UserRepresentation;
|
|||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
|
||||
import org.keycloak.testsuite.pages.AbstractPage;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
|
@ -187,7 +188,7 @@ public abstract class AbstractX509AuthenticationTest extends AbstractTestRealmKe
|
|||
* @return server home directory. This directory is supposed to contain client key, certificate and CRLs used in the tests
|
||||
*/
|
||||
protected static String getAuthServerHome() {
|
||||
String authServerHome = System.getProperty("auth.server.home");
|
||||
String authServerHome = System.getProperty(AuthServerTestEnricher.AUTH_SERVER_HOME_PROPERTY);
|
||||
if (authServerHome == null) {
|
||||
return null;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue