diff --git a/testsuite/integration-arquillian/HOW-TO-RUN.md b/testsuite/integration-arquillian/HOW-TO-RUN.md
index e599c013a1..40623b8310 100644
--- a/testsuite/integration-arquillian/HOW-TO-RUN.md
+++ b/testsuite/integration-arquillian/HOW-TO-RUN.md
@@ -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
diff --git a/testsuite/integration-arquillian/pom.xml b/testsuite/integration-arquillian/pom.xml
index 4f2b5ba420..3338b0b841 100644
--- a/testsuite/integration-arquillian/pom.xml
+++ b/testsuite/integration-arquillian/pom.xml
@@ -89,11 +89,6 @@
h2
${h2.version}
- org.h2.Driver
- keycloak
- sa
-
- jdbc:h2:mem:test;MVCC=TRUE;DB_CLOSE_DELAY=-1
DEFAULT
true
@@ -393,6 +388,23 @@
+
+
+ db-default-properties
+
+
+ !dballocator.uri
+
+
+
+ org.h2.Driver
+ keycloak
+ sa
+
+ jdbc:h2:mem:test;MVCC=TRUE;DB_CLOSE_DELAY=-1
+
+
db-mysql
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java
index d1da336c40..f897303597 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java
@@ -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");
- startContainerEvent.fire(new StartContainer(suiteContext.getAuthServerInfo().getArquillianContainer()));
+
+ 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");
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/SqlUtils.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/SqlUtils.java
new file mode 100644
index 0000000000..cfc4a1c313
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/SqlUtils.java
@@ -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 Marek Posolda
+ */
+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();
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/AbstractX509AuthenticationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/AbstractX509AuthenticationTest.java
index 0c4991c88f..1f7f9f5516 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/AbstractX509AuthenticationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/AbstractX509AuthenticationTest.java
@@ -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;
}