diff options
author | Eric Hartmann <hartmann.eric@gmail.com> | 2017-06-19 16:41:19 +0200 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@sonarsource.com> | 2017-07-05 21:02:58 +0200 |
commit | 884e73d80752e779949284176173028f5feb0100 (patch) | |
tree | e341f05bb80751f4056a7615d360b6fc9dd7d32e /tests | |
parent | e964104ca94fdd7b975dedbbe8b15eb578b41f53 (diff) | |
download | sonarqube-884e73d80752e779949284176173028f5feb0100.tar.gz sonarqube-884e73d80752e779949284176173028f5feb0100.zip |
MMF-935 experiment ES resilience of user creation
Diffstat (limited to 'tests')
-rw-r--r-- | tests/pom.xml | 28 | ||||
-rw-r--r-- | tests/resilience/user_indexer.btm | 23 | ||||
-rw-r--r-- | tests/src/test/java/org/sonarqube/tests/Category5Suite.java | 4 | ||||
-rw-r--r-- | tests/src/test/java/org/sonarqube/tests/user/UserEsResilienceTest.java | 144 |
4 files changed, 198 insertions, 1 deletions
diff --git a/tests/pom.xml b/tests/pom.xml index 2409cdfda33..4ac41b49d2f 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -157,6 +157,34 @@ </includes> </configuration> </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-dependency-plugin</artifactId> + <executions> + <execution> + <id>copy-byteman-for-resilience-tests</id> + <phase>generate-test-resources</phase> + <goals> + <goal>copy</goal> + </goals> + <configuration> + <artifactItems> + <artifactItem> + <groupId>org.jboss.byteman</groupId> + <artifactId>byteman</artifactId> + <version>3.0.10</version> + <overWrite>false</overWrite> + <destFileName>byteman.jar</destFileName> + </artifactItem> + </artifactItems> + <outputDirectory>${project.basedir}/target</outputDirectory> + <overWriteReleases>false</overWriteReleases> + <overWriteSnapshots>false</overWriteSnapshots> + </configuration> + </execution> + </executions> + </plugin> + </plugins> </build> diff --git a/tests/resilience/user_indexer.btm b/tests/resilience/user_indexer.btm new file mode 100644 index 00000000000..a80c2910d32 --- /dev/null +++ b/tests/resilience/user_indexer.btm @@ -0,0 +1,23 @@ +# sonar.web.javaAdditionalOpts=-javaagent:/path/to/byteman-3.0.10/lib/byteman.jar=script:/path/to/user_indexer.btm,boot:/path/to/byteman-3.0.10/lib/byteman.jar +# sonar.search.recovery.delayInMs=10000 +# sonar.search.recovery.minAgeInMs=30000 + +RULE make indexing of users silently fail +CLASS org.sonar.server.user.index.UserIndexer +METHOD postCommit +COMPILE +AT ENTRY +BIND logins:Collection = $logins +IF logins.contains("error") +DO RETURN +ENDRULE + +RULE make indexing of users fail +CLASS org.sonar.server.user.index.UserIndexer +METHOD postCommit +COMPILE +AT ENTRY +BIND logins:Collection = $logins +IF logins.contains("crash") +DO THROW new RuntimeException("Fail to index users to Elasticsearch because a user 'crash' has been given") +ENDRULE diff --git a/tests/src/test/java/org/sonarqube/tests/Category5Suite.java b/tests/src/test/java/org/sonarqube/tests/Category5Suite.java index 5335ef37b3b..63b7c1f1628 100644 --- a/tests/src/test/java/org/sonarqube/tests/Category5Suite.java +++ b/tests/src/test/java/org/sonarqube/tests/Category5Suite.java @@ -31,6 +31,7 @@ import org.sonarqube.tests.updateCenter.UpdateCenterTest; import org.sonarqube.tests.user.OnboardingTest; import org.sonarqube.tests.user.RealmAuthenticationTest; import org.sonarqube.tests.user.SsoAuthenticationTest; +import org.sonarqube.tests.user.UserEsResilienceTest; /** * This suite is reserved to the tests that start their own instance of Orchestrator. @@ -49,7 +50,8 @@ import org.sonarqube.tests.user.SsoAuthenticationTest; RealmAuthenticationTest.class, SsoAuthenticationTest.class, OnboardingTest.class, - BuiltInQualityProfilesNotificationTest.class + BuiltInQualityProfilesNotificationTest.class, + UserEsResilienceTest.class }) public class Category5Suite { diff --git a/tests/src/test/java/org/sonarqube/tests/user/UserEsResilienceTest.java b/tests/src/test/java/org/sonarqube/tests/user/UserEsResilienceTest.java new file mode 100644 index 00000000000..10c3a441a53 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/user/UserEsResilienceTest.java @@ -0,0 +1,144 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.user; + +import com.sonar.orchestrator.Orchestrator; +import java.io.File; +import java.util.concurrent.TimeUnit; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.WsUsers.CreateWsResponse.User; +import org.sonarqube.ws.client.user.SearchRequest; +import org.sonarqube.ws.client.user.UpdateRequest; + +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.expectHttpError; + +public class UserEsResilienceTest { + + @ClassRule + public static final Orchestrator orchestrator = Orchestrator.builderEnv() + .setServerProperty("sonar.web.javaAdditionalOpts", + format("-javaagent:%s=script:%s,boot:%s", findBytemanJar(), findBytemanScript(), findBytemanJar())) + .setServerProperty("sonar.search.recovery.delayInMs", "1000") + .setServerProperty("sonar.search.recovery.minAgeInMs", "3000") + .build(); + + @Rule + public TestRule timeout = new DisableOnDebug(Timeout.builder() + .withLookingForStuckThread(true) + .withTimeout(60L, TimeUnit.SECONDS) + .build()); + + @Rule + public Tester tester = new Tester(orchestrator); + + @Test + public void creation_and_update_of_user_are_resilient_to_indexing_errors() throws Exception { + String login = "error"; + + // creation of user succeeds but index is not up-to-date (indexing + // failures are not propagated to web services) + User user = tester.users().generate(u -> u.setLogin(login)); + + // user exists in db, it can't be created again. + // However he's not indexed. + expectHttpError(400, "An active user with login '" + login + "' already exists", + () -> tester.users().generate(u -> u.setLogin(login))); + assertThat(isReturnedInSearch(user.getLogin())).isFalse(); + + while (!isReturnedInSearch(user.getLogin())) { + // user is indexed by the recovery daemon, which runs every 5 seconds + Thread.sleep(1_000L); + } + + // update the name. Db operation succeeds but not ES indexing. + // Renaming is not propagated to index as long as recovery does not + // run. + String newName = "renamed"; + tester.users().service().update(UpdateRequest.builder().setLogin(login).setName(newName).build()); + assertThat(isReturnedInSearch(newName)).isFalse(); + + while (!isReturnedInSearch(newName)) { + // user is indexed by the recovery daemon, which runs every 5 seconds + Thread.sleep(1_000L); + } + } + + @Test + public void creation_and_update_of_user_are_resilient_to_indexing_crash() throws Exception { + String login = "crash"; + + // creation of user succeeds but index is not up-to-date (indexing + // crashes are not propagated to web services) + expectHttpError(500, () -> tester.users().generate(u -> u.setLogin(login))); + + // user exists in db, it can't be created again. + // However he's not indexed. + expectHttpError(400, "An active user with login '" + login + "' already exists", + () -> tester.users().generate(u -> u.setLogin(login))); + assertThat(isReturnedInSearch(login)).isFalse(); + + while (!isReturnedInSearch(login)) { + // user is indexed by the recovery daemon, which runs every 5 seconds + Thread.sleep(1_000L); + } + + // update the name. Db operation succeeds but ES indexing crashes. + // Renaming is not propagated to index as long as recovery does not + // run. + String newName = "renamed"; + expectHttpError(500, () -> tester.users().service().update(UpdateRequest.builder().setLogin(login).setName(newName).build())); + assertThat(isReturnedInSearch(newName)).isFalse(); + + while (!isReturnedInSearch(newName)) { + // user is indexed by the recovery daemon, which runs every 5 seconds + Thread.sleep(1_000L); + } + } + + private boolean isReturnedInSearch(String name) { + return tester.users().service().search(SearchRequest.builder().setQuery(name).build()).getUsersCount() == 1L; + } + + private static String findBytemanJar() { + // see pom.xml, Maven copies and renames the artifact. + File jar = new File("target/byteman.jar"); + if (!jar.exists()) { + throw new IllegalStateException("Can't find " + jar + ". Please execute 'mvn generate-test-resources' on integration tests once."); + } + return jar.getAbsolutePath(); + } + + private static String findBytemanScript() { + // see pom.xml, Maven copies and renames the artifact. + File script = new File("resilience/user_indexer.btm"); + if (!script.exists()) { + throw new IllegalStateException("Can't find " + script); + } + return script.getAbsolutePath(); + } +} |