/* * Copyright 2016 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.arquillian; import org.jboss.arquillian.container.spi.Container; import org.jboss.arquillian.container.spi.ContainerRegistry; import org.jboss.arquillian.container.spi.event.StartContainer; import org.jboss.arquillian.container.spi.event.StartSuiteContainers; import org.jboss.arquillian.container.spi.event.StopContainer; import org.jboss.arquillian.core.api.Event; import org.jboss.arquillian.core.api.Instance; import org.jboss.arquillian.core.api.InstanceProducer; import org.jboss.arquillian.core.api.annotation.Inject; import org.jboss.arquillian.core.api.annotation.Observes; import org.jboss.arquillian.test.spi.annotation.ClassScoped; import org.jboss.arquillian.test.spi.annotation.SuiteScoped; import org.jboss.arquillian.test.spi.event.suite.AfterClass; 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.representations.idm.RealmRepresentation; import org.keycloak.testsuite.client.KeycloakTestingClient; import org.keycloak.testsuite.util.LogChecker; import org.keycloak.testsuite.util.OAuthClient; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import javax.ws.rs.NotFoundException; /** * * @author tkyjovsk * @author vramik */ public class AuthServerTestEnricher { protected final Logger log = Logger.getLogger(this.getClass()); @Inject private Instance<ContainerRegistry> containerRegistry; @Inject private Event<StartContainer> startContainerEvent; @Inject private Event<StopContainer> stopContainerEvent; private static final String AUTH_SERVER_CONTAINER_DEFAULT = "auth-server-undertow"; private static final String AUTH_SERVER_CONTAINER_PROPERTY = "auth.server.container"; public static final String AUTH_SERVER_CONTAINER = System.getProperty(AUTH_SERVER_CONTAINER_PROPERTY, AUTH_SERVER_CONTAINER_DEFAULT); private static final String AUTH_SERVER_CLUSTER_PROPERTY = "auth.server.cluster"; public static final boolean AUTH_SERVER_CLUSTER = Boolean.parseBoolean(System.getProperty(AUTH_SERVER_CLUSTER_PROPERTY, "false")); private static final boolean AUTH_SERVER_UNDERTOW_CLUSTER = Boolean.parseBoolean(System.getProperty("auth.server.undertow.cluster", "false")); private static final Boolean START_MIGRATION_CONTAINER = "auto".equals(System.getProperty("migration.mode")) || "manual".equals(System.getProperty("migration.mode")); @Inject @SuiteScoped private InstanceProducer<SuiteContext> suiteContextProducer; private SuiteContext suiteContext; @Inject @ClassScoped private InstanceProducer<TestContext> testContextProducer; @Inject @ClassScoped private InstanceProducer<OAuthClient> oAuthClientProducer; public static String getAuthServerContextRoot() { return getAuthServerContextRoot(0); } public static String getAuthServerContextRoot(int clusterPortOffset) { String host = System.getProperty("auth.server.host", "localhost"); int httpPort = Integer.parseInt(System.getProperty("auth.server.http.port")); // property must be set int httpsPort = Integer.parseInt(System.getProperty("auth.server.https.port")); // property must be set boolean sslRequired = Boolean.parseBoolean(System.getProperty("auth.server.ssl.required")); String scheme = sslRequired ? "https" : "http"; int port = sslRequired ? httpsPort : httpPort; return String.format("%s://%s:%s", scheme, host, port + clusterPortOffset); } public void initializeSuiteContext(@Observes(precedence = 2) BeforeSuite event) { Set<ContainerInfo> containers = new LinkedHashSet<>(); for (Container c : containerRegistry.get().getContainers()) { containers.add(new ContainerInfo(c)); } suiteContext = new SuiteContext(containers); String authServerFrontend = null; if (AUTH_SERVER_CLUSTER) { // if cluster mode enabled, load-balancer is the frontend for (ContainerInfo c : containers) { if (c.getQualifier().startsWith("auth-server-balancer")) { authServerFrontend = c.getQualifier(); } } if (authServerFrontend != null) { log.info("Using frontend container: " + authServerFrontend); } else { throw new IllegalStateException("Not found frontend container"); } } else { authServerFrontend = AUTH_SERVER_CONTAINER; // single-node mode } String authServerBackend = AUTH_SERVER_CONTAINER + "-backend"; int backends = 0; for (ContainerInfo container : suiteContext.getContainers()) { // frontend if (container.getQualifier().equals(authServerFrontend)) { updateWithAuthServerInfo(container); suiteContext.setAuthServerInfo(container); } // backends if (AUTH_SERVER_CLUSTER && container.getQualifier().startsWith(authServerBackend)) { updateWithAuthServerInfo(container, ++backends); suiteContext.getAuthServerBackendsInfo().add(container); } } // Setup with 2 undertow backend nodes and no loadbalancer. // if (AUTH_SERVER_UNDERTOW_CLUSTER && suiteContext.getAuthServerInfo() == null && !suiteContext.getAuthServerBackendsInfo().isEmpty()) { // suiteContext.setAuthServerInfo(suiteContext.getAuthServerBackendsInfo().get(0)); // } // validate auth server setup if (suiteContext.getAuthServerInfo() == null) { throw new RuntimeException(String.format("No auth server container matching '%s' found in arquillian.xml.", authServerFrontend)); } if (AUTH_SERVER_CLUSTER && suiteContext.getAuthServerBackendsInfo().isEmpty()) { throw new RuntimeException(String.format("No auth server container matching '%sN' found in arquillian.xml.", authServerBackend)); } if (START_MIGRATION_CONTAINER) { // init migratedAuthServerInfo for (ContainerInfo container : suiteContext.getContainers()) { // migrated auth server if (container.getQualifier().equals("auth-server-jboss-migration")) { updateWithAuthServerInfo(container); suiteContext.setMigratedAuthServerInfo(container); } } // validate setup if (suiteContext.getMigratedAuthServerInfo() == null) { throw new RuntimeException(String.format("Migration test was enabled but no auth server from which to migrate was activated. " + "A container matching auth-server-jboss-migration needs to be enabled in arquillian.xml.")); } } suiteContextProducer.set(suiteContext); log.info("\n\n" + suiteContext); } private ContainerInfo updateWithAuthServerInfo(ContainerInfo authServerInfo) { return updateWithAuthServerInfo(authServerInfo, 0); } private ContainerInfo updateWithAuthServerInfo(ContainerInfo authServerInfo, int clusterPortOffset) { try { authServerInfo.setContextRoot(new URL(getAuthServerContextRoot(clusterPortOffset))); } catch (MalformedURLException ex) { throw new IllegalArgumentException(ex); } return authServerInfo; } public void startMigratedContainer(@Observes(precedence = 3) StartSuiteContainers event) { if (suiteContext.isAuthServerMigrationEnabled()) { log.info("\n\n### Starting keycloak " + System.getProperty("migrated.auth.server.version", "- previous") + " ###\n\n"); startContainerEvent.fire(new StartContainer(suiteContext.getMigratedAuthServerInfo().getArquillianContainer())); } } public void runPreMigrationTask(@Observes(precedence = 2) StartSuiteContainers event) throws Exception { if (suiteContext.isAuthServerMigrationEnabled()) { log.info("\n\n### Run preMigration task on keycloak " + System.getProperty("migrated.auth.server.version", "- previous") + " ###\n\n"); suiteContext.getMigrationContext().runPreMigrationTask(); } } public void stopMigratedContainer(@Observes(precedence = 1) StartSuiteContainers event) { if (suiteContext.isAuthServerMigrationEnabled()) { log.info("## STOP old container: " + suiteContext.getMigratedAuthServerInfo().getQualifier()); stopContainerEvent.fire(new StopContainer(suiteContext.getMigratedAuthServerInfo().getArquillianContainer())); } } public void checkServerLogs(@Observes(precedence = -1) BeforeSuite event) throws IOException, InterruptedException { boolean checkLog = Boolean.parseBoolean(System.getProperty("auth.server.log.check", "true")); if (checkLog && suiteContext.getAuthServerInfo().isJBossBased()) { String jbossHomePath = suiteContext.getAuthServerInfo().getProperties().get("jbossHome"); LogChecker.checkJBossServerLog(jbossHomePath); } } public void initializeTestContext(@Observes(precedence = 2) BeforeClass event) { TestContext testContext = new TestContext(suiteContext, event.getTestClass().getJavaClass()); testContextProducer.set(testContext); } public void initializeOAuthClient(@Observes(precedence = 3) BeforeClass event) { OAuthClient oAuthClient = new OAuthClient(); oAuthClientProducer.set(oAuthClient); } public void afterClass(@Observes(precedence = 2) AfterClass event) { TestContext testContext = testContextProducer.get(); List<RealmRepresentation> testRealmReps = testContext.getTestRealmReps(); Keycloak adminClient = testContext.getAdminClient(); KeycloakTestingClient testingClient = testContext.getTestingClient(); if (testRealmReps != null) { log.info("removing test realms after test class"); for (RealmRepresentation testRealm : testRealmReps) { String realmName = testRealm.getRealm(); log.info("removing realm: " + realmName); try { adminClient.realms().realm(realmName).remove(); } catch (NotFoundException e) { // Ignore } } } if (adminClient != null) { adminClient.close(); } if (testingClient != null) { testingClient.close(); } } }