aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorEric Hartmann <hartmann.eric@gmail.com>2017-06-19 16:41:19 +0200
committerSimon Brandhof <simon.brandhof@sonarsource.com>2017-07-05 21:02:58 +0200
commit884e73d80752e779949284176173028f5feb0100 (patch)
treee341f05bb80751f4056a7615d360b6fc9dd7d32e /tests
parente964104ca94fdd7b975dedbbe8b15eb578b41f53 (diff)
downloadsonarqube-884e73d80752e779949284176173028f5feb0100.tar.gz
sonarqube-884e73d80752e779949284176173028f5feb0100.zip
MMF-935 experiment ES resilience of user creation
Diffstat (limited to 'tests')
-rw-r--r--tests/pom.xml28
-rw-r--r--tests/resilience/user_indexer.btm23
-rw-r--r--tests/src/test/java/org/sonarqube/tests/Category5Suite.java4
-rw-r--r--tests/src/test/java/org/sonarqube/tests/user/UserEsResilienceTest.java144
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();
+ }
+}