Merge branch 'evict-cache-plugin'

* evict-cache-plugin:
  Replace EasyMock with Mockito
  Compile against 2.13.3
  Change docs links to actual file extension (.md)
  Return "Bad Request" status code on failure to parse request entity
  Stop retrying if thread is interrupted
  Print proper name in show-queue for cache eviction
  Initial version of evict-cache plugin

evict-cache-plugin branch was created by fetching stable-2.13
(9691f0e417cdd49f8e66e76176be05b29076a4ee) branch of repository
https://gerrit.googlesource.com/plugins/evict-cache and pushing it to
this repository.

This merge adds the third of the 4 plugins that will be merged in this
repository. websession-flatfile will be done in follow up commit.

The reason for choosing stable-2.13 instead of master is that the only
commits done in master that are not in stable-2.13 were to adapt to
Gerrit master branch. Since all the evict-cache features/bug fixes are
in stable-2.13, use this branch so high-availability will support Gerrit
2.13.

Change-Id: Icf5a140dd0e30d00f0f6c52cdda5185fe068413e
diff --git a/.buckconfig b/.buckconfig
index 4c18a27..5c0ead0 100644
--- a/.buckconfig
+++ b/.buckconfig
@@ -1,13 +1,15 @@
 [alias]
-  evict-cache = //:evict-cache
-  plugin = //:evict-cache
-  src = //:evict-cache-sources
+  high-availability = //:high-availability
+  plugin = //:high-availability
+  src = //:high-availability-sources
 
 [java]
+  jar_spool_mode = direct_to_jar
   src_roots = java, resources
 
 [project]
-  ignore = .git
+  ignore = .git, eclipse-out/
+  parallel_parsing = true
 
 [cache]
   mode = dir
diff --git a/.gitignore b/.gitignore
index b37f9a6..c27e17f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-/.buckd
+/.buckd/
 /.buckversion
 /.classpath
 /.project
@@ -6,4 +6,4 @@
 /.watchmanconfig
 /buck-out/
 /bucklets
-/eclipse-out
+/eclipse-out/
diff --git a/BUCK b/BUCK
index acdb2af..dcbf446 100644
--- a/BUCK
+++ b/BUCK
@@ -5,38 +5,30 @@
 SOURCES = glob(['src/main/java/**/*.java'])
 RESOURCES = glob(['src/main/resources/**/*'])
 
-DEPS = [
+TEST_DEPS = GERRIT_PLUGIN_API + GERRIT_TESTS + [
+  ':high-availability__plugin',
+  ':mockito',
   ':wiremock',
 ]
 
-PROVIDED_DEPS = GERRIT_TESTS + [
-  '//lib:gson',
-]
-
-TEST_DEPS = GERRIT_PLUGIN_API + PROVIDED_DEPS + DEPS + [
-  ':evict-cache__plugin',
-  ':mockito',
-]
-
 gerrit_plugin(
-  name = 'evict-cache',
+  name = 'high-availability',
   srcs = SOURCES,
   resources = RESOURCES,
   manifest_entries = [
-    'Gerrit-PluginName: evict-cache',
+    'Gerrit-PluginName: high-availability',
     'Gerrit-ApiType: plugin',
-    'Gerrit-Module: com.ericsson.gerrit.plugins.evictcache.Module',
-    'Gerrit-HttpModule: com.ericsson.gerrit.plugins.evictcache.HttpModule',
-    'Implementation-Title: evict-cache plugin',
-    'Implementation-URL: https://gerrit-review.googlesource.com/#/admin/projects/plugins/evict-cache',
+    'Gerrit-Module: com.ericsson.gerrit.plugins.highavailability.Module',
+    'Gerrit-HttpModule: com.ericsson.gerrit.plugins.highavailability.HttpModule',
+    'Implementation-Title: high-availability plugin',
+    'Implementation-URL: https://gerrit-review.googlesource.com/#/admin/projects/plugins/high-availability',
     'Implementation-Vendor: Ericsson',
   ],
-  provided_deps = PROVIDED_DEPS,
-  deps = DEPS,
+  provided_deps = GERRIT_TESTS,
 )
 
 java_sources(
-  name = 'evict-cache-sources',
+  name = 'high-availability-sources',
   srcs = SOURCES + RESOURCES,
 )
 
@@ -46,9 +38,9 @@
 )
 
 java_test(
-  name = 'evict-cache_tests',
+  name = 'high-availability_tests',
   srcs = glob(['src/test/java/**/*.java']),
-  labels = ['evict-cache'],
+  labels = ['high-availability'],
   deps = TEST_DEPS,
 )
 
@@ -64,7 +56,7 @@
   name = 'mockito',
   id = 'org.mockito:mockito-core:2.5.0',
   sha1 = 'be28d46a52c7f2563580adeca350145e9ce916f8',
-  license = 'MIT',
+  license = 'DO_NOT_DISTRIBUTE',
   deps = [
     ':byte-buddy',
     ':objenesis',
diff --git a/lib/BUCK b/lib/BUCK
deleted file mode 100644
index 8892994..0000000
--- a/lib/BUCK
+++ /dev/null
@@ -1,8 +0,0 @@
-include_defs('//bucklets/maven_jar.bucklet')
-
-maven_jar(
-  name = 'gson',
-  id = 'com.google.code.gson:gson:2.1',
-  sha1 = '2e66da15851f9f5b5079228f856c2f090ba98c38',
-  license = 'Apache2.0',
-)
diff --git a/lib/gerrit/BUCK b/lib/gerrit/BUCK
index f6c68a4..8a21820 100644
--- a/lib/gerrit/BUCK
+++ b/lib/gerrit/BUCK
@@ -1,21 +1,21 @@
 include_defs('//bucklets/maven_jar.bucklet')
 
-VER = '2.13.3'
+VER = '2.13.6'
 REPO = MAVEN_CENTRAL
 
 maven_jar(
-  name = 'plugin-api',
-  id = 'com.google.gerrit:gerrit-plugin-api:' + VER,
-  sha1 = '78df190269b0d5a4bc4f61ab3a66a49252b779eb',
+  name = 'acceptance-framework',
+  id = 'com.google.gerrit:gerrit-acceptance-framework:' + VER,
+  sha1 = '53a5ffbc3ce6842b7145fd11abcc1dc8503b124f',
   license = 'Apache2.0',
   attach_source = False,
   repository = REPO,
 )
 
 maven_jar(
-  name = 'acceptance-framework',
-  id = 'com.google.gerrit:gerrit-acceptance-framework:' + VER,
-  sha1 = '76eceefa7e31c6945513c36c4d106066d1df3e63',
+  name = 'plugin-api',
+  id = 'com.google.gerrit:gerrit-plugin-api:' + VER,
+  sha1 = '1a5d650c72ebc36f4ad522d5481d5ed690bec8bf',
   license = 'Apache2.0',
   attach_source = False,
   repository = REPO,
diff --git a/src/main/java/com/ericsson/gerrit/plugins/evictcache/Configuration.java b/src/main/java/com/ericsson/gerrit/plugins/evictcache/Configuration.java
deleted file mode 100644
index e342f88..0000000
--- a/src/main/java/com/ericsson/gerrit/plugins/evictcache/Configuration.java
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (C) 2015 Ericsson
-//
-// 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 com.ericsson.gerrit.plugins.evictcache;
-
-import com.google.common.base.CharMatcher;
-import com.google.common.base.Strings;
-import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.gerrit.server.config.PluginConfig;
-import com.google.gerrit.server.config.PluginConfigFactory;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-
-@Singleton
-class Configuration {
-  private static final int DEFAULT_TIMEOUT_MS = 5000;
-  private static final int DEFAULT_MAX_TRIES = 5;
-  private static final int DEFAULT_RETRY_INTERVAL = 1000;
-  private static final int DEFAULT_THREAD_POOL_SIZE = 1;
-
-  private final String url;
-  private final String user;
-  private final String password;
-  private final int connectionTimeout;
-  private final int socketTimeout;
-  private final int maxTries;
-  private final int retryInterval;
-  private final int threadPoolSize;
-
-  @Inject
-  Configuration(PluginConfigFactory config,
-      @PluginName String pluginName) {
-    PluginConfig cfg = config.getFromGerritConfig(pluginName, true);
-    url = Strings.nullToEmpty(cfg.getString("url"));
-    user = Strings.nullToEmpty(cfg.getString("user"));
-    password = Strings.nullToEmpty(cfg.getString("password"));
-    connectionTimeout = cfg.getInt("connectionTimeout", DEFAULT_TIMEOUT_MS);
-    socketTimeout = cfg.getInt("socketTimeout", DEFAULT_TIMEOUT_MS);
-    maxTries = cfg.getInt("maxTries", DEFAULT_MAX_TRIES);
-    retryInterval = cfg.getInt("retryInterval", DEFAULT_RETRY_INTERVAL);
-    threadPoolSize = cfg.getInt("threadPoolSize", DEFAULT_THREAD_POOL_SIZE);
-  }
-
-  int getConnectionTimeout() {
-    return connectionTimeout;
-  }
-
-  int getMaxTries() {
-    return maxTries;
-  }
-
-  int getRetryInterval() {
-    return retryInterval;
-  }
-
-  int getSocketTimeout() {
-    return socketTimeout;
-  }
-
-  String getUrl() {
-    return CharMatcher.is('/').trimTrailingFrom(url);
-  }
-
-  String getUser() {
-    return user;
-  }
-
-  String getPassword() {
-    return password;
-  }
-
-  int getThreadPoolSize() {
-    return threadPoolSize;
-  }
-}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheExecutor.java b/src/main/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheExecutor.java
deleted file mode 100644
index 8106f55..0000000
--- a/src/main/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheExecutor.java
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2015 Ericsson
-//
-// 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 com.ericsson.gerrit.plugins.evictcache;
-
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import com.google.inject.BindingAnnotation;
-
-import java.lang.annotation.Retention;
-
-@Retention(RUNTIME)
-@BindingAnnotation
-@interface EvictCacheExecutor {
-}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/evictcache/HttpSession.java b/src/main/java/com/ericsson/gerrit/plugins/evictcache/HttpSession.java
deleted file mode 100644
index 35fbd65..0000000
--- a/src/main/java/com/ericsson/gerrit/plugins/evictcache/HttpSession.java
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (C) 2015 Ericsson
-//
-// 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 com.ericsson.gerrit.plugins.evictcache;
-
-import com.google.common.net.MediaType;
-import com.google.inject.Inject;
-
-import com.ericsson.gerrit.plugins.evictcache.CacheResponseHandler.CacheResult;
-
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.CloseableHttpClient;
-
-import java.io.IOException;
-
-class HttpSession {
-  private final CloseableHttpClient httpClient;
-  private final String url;
-
-  @Inject
-  HttpSession(CloseableHttpClient httpClient,
-      @SyncUrl String url) {
-    this.httpClient = httpClient;
-    this.url = url;
-  }
-
-  CacheResult post(String endpoint, String json) throws IOException {
-    HttpPost post = new HttpPost(url + endpoint);
-    StringEntity params = new StringEntity(json.trim());
-    post.addHeader("Content-Type", MediaType.JSON_UTF_8.toString());
-    post.addHeader("Accept", MediaType.JSON_UTF_8.toString());
-    post.setEntity(params);
-    return httpClient.execute(post, new CacheResponseHandler());
-  }
-}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/evictcache/Module.java b/src/main/java/com/ericsson/gerrit/plugins/evictcache/Module.java
deleted file mode 100644
index 28ba756..0000000
--- a/src/main/java/com/ericsson/gerrit/plugins/evictcache/Module.java
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (C) 2015 Ericsson
-//
-// 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 com.ericsson.gerrit.plugins.evictcache;
-
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.gerrit.server.cache.CacheRemovalListener;
-import com.google.inject.Provides;
-import com.google.inject.Scopes;
-
-import org.apache.http.impl.client.CloseableHttpClient;
-
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-
-class Module extends LifecycleModule {
-
-  @Override
-  protected void configure() {
-    bind(CloseableHttpClient.class).toProvider(HttpClientProvider.class)
-        .in(Scopes.SINGLETON);
-    bind(Configuration.class);
-    bind(HttpSession.class);
-    bind(RestSession.class);
-    bind(ScheduledThreadPoolExecutor.class)
-        .annotatedWith(EvictCacheExecutor.class)
-        .toProvider(EvictCacheExecutorProvider.class);
-    listener().to(EvictCacheExecutorProvider.class);
-    DynamicSet.bind(binder(), CacheRemovalListener.class).to(
-        EvictCacheHandler.class);
-  }
-
-  @Provides
-  @SyncUrl
-  String syncUrl(Configuration config) {
-    return config.getUrl();
-  }
-}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/evictcache/RestSession.java b/src/main/java/com/ericsson/gerrit/plugins/evictcache/RestSession.java
deleted file mode 100644
index 1295ce2..0000000
--- a/src/main/java/com/ericsson/gerrit/plugins/evictcache/RestSession.java
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright (C) 2015 Ericsson
-//
-// 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 com.ericsson.gerrit.plugins.evictcache;
-
-import com.google.common.base.Joiner;
-import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.inject.Inject;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-
-class RestSession {
-  private static final Logger log = LoggerFactory.getLogger(RestSession.class);
-  private static final String EVICT = "evict";
-  private final HttpSession httpSession;
-  private final String pluginName;
-
-  @Inject
-  RestSession(HttpSession httpClient,
-      @PluginName String pluginName) {
-    this.httpSession = httpClient;
-    this.pluginName = pluginName;
-  }
-
-  boolean evict(String sourceName, String cacheName, Object key) {
-    try {
-      String json = GsonParser.toJson(cacheName, key);
-      String buildEndpoint = buildEndpoint(pluginName, sourceName, cacheName);
-      return httpSession
-          .post(buildEndpoint, json)
-          .isSuccessful();
-    } catch (IOException e) {
-      log.error("Error trying to evict for cache " + cacheName, e);
-      return false;
-    }
-  }
-
-  private String buildEndpoint(String pluginName, String sourceName,
-      String cacheName) {
-    return Joiner.on("/").join("/plugins", pluginName, sourceName, EVICT, cacheName);
-  }
-}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
new file mode 100644
index 0000000..13f202d
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
@@ -0,0 +1,116 @@
+// Copyright (C) 2015 Ericsson
+//
+// 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 com.ericsson.gerrit.plugins.highavailability;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+@Singleton
+public class Configuration {
+  private static final Logger log = LoggerFactory.getLogger(Configuration.class);
+
+  private static final int DEFAULT_TIMEOUT_MS = 5000;
+  private static final int DEFAULT_MAX_TRIES = 5;
+  private static final int DEFAULT_RETRY_INTERVAL = 1000;
+  private static final int DEFAULT_THREAD_POOL_SIZE = 1;
+
+  private final String url;
+  private final String user;
+  private final String password;
+  private final int connectionTimeout;
+  private final int socketTimeout;
+  private final int maxTries;
+  private final int retryInterval;
+  private final int indexThreadPoolSize;
+  private final int eventThreadPoolSize;
+  private final int cacheThreadPoolSize;
+
+  @Inject
+  Configuration(PluginConfigFactory config,
+      @PluginName String pluginName) {
+    PluginConfig cfg = config.getFromGerritConfig(pluginName, true);
+    url = Strings.nullToEmpty(cfg.getString("url"));
+    user = Strings.nullToEmpty(cfg.getString("user"));
+    password = Strings.nullToEmpty(cfg.getString("password"));
+    connectionTimeout = getInt(cfg, "connectionTimeout", DEFAULT_TIMEOUT_MS);
+    socketTimeout = getInt(cfg, "socketTimeout", DEFAULT_TIMEOUT_MS);
+    maxTries = getInt(cfg, "maxTries", DEFAULT_MAX_TRIES);
+    retryInterval = getInt(cfg, "retryInterval", DEFAULT_RETRY_INTERVAL);
+    indexThreadPoolSize =
+        getInt(cfg, "indexThreadPoolSize", DEFAULT_THREAD_POOL_SIZE);
+    eventThreadPoolSize =
+        getInt(cfg, "eventThreadPoolSize", DEFAULT_THREAD_POOL_SIZE);
+    cacheThreadPoolSize =
+        getInt(cfg, "cacheThreadPoolSize", DEFAULT_THREAD_POOL_SIZE);
+  }
+
+  private int getInt(PluginConfig cfg, String name, int defaultValue) {
+    try {
+      return cfg.getInt(name, defaultValue);
+    } catch (IllegalArgumentException e) {
+      log.error(String.format(
+          "invalid value for %s; using default value %d", name, defaultValue));
+      return defaultValue;
+    }
+  }
+
+  public int getConnectionTimeout() {
+    return connectionTimeout;
+  }
+
+  public int getMaxTries() {
+    return maxTries;
+  }
+
+  public int getRetryInterval() {
+    return retryInterval;
+  }
+
+  public int getSocketTimeout() {
+    return socketTimeout;
+  }
+
+  public String getUrl() {
+    return CharMatcher.is('/').trimTrailingFrom(url);
+  }
+
+  public String getUser() {
+    return user;
+  }
+
+  public String getPassword() {
+    return password;
+  }
+
+  public int getIndexThreadPoolSize() {
+    return indexThreadPoolSize;
+  }
+
+  public int getEventThreadPoolSize() {
+    return eventThreadPoolSize;
+  }
+
+  public int getCacheThreadPoolSize() {
+    return cacheThreadPoolSize;
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/evictcache/HttpModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/HttpModule.java
similarity index 78%
rename from src/main/java/com/ericsson/gerrit/plugins/evictcache/HttpModule.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/HttpModule.java
index 27cf205..4a83e92 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/evictcache/HttpModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/HttpModule.java
@@ -12,13 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability;
 
 import com.google.gerrit.httpd.plugins.HttpPluginModule;
 
+import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.RestForwarderServletModule;
+
 class HttpModule extends HttpPluginModule {
   @Override
   protected void configureServlets() {
-    serve("/gerrit/evict/*").with(EvictCacheRestApiServlet.class);
+    install(new RestForwarderServletModule());
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java
new file mode 100644
index 0000000..8481974
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2015 Ericsson
+//
+// 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 com.ericsson.gerrit.plugins.highavailability;
+
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.inject.Scopes;
+
+import com.ericsson.gerrit.plugins.highavailability.cache.CacheModule;
+import com.ericsson.gerrit.plugins.highavailability.event.EventModule;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.RestForwarderModule;
+import com.ericsson.gerrit.plugins.highavailability.index.IndexModule;
+
+class Module extends LifecycleModule {
+
+  @Override
+  protected void configure() {
+    bind(Configuration.class).in(Scopes.SINGLETON);
+    install(new RestForwarderModule());
+    install(new EventModule());
+    install(new IndexModule());
+    install(new CacheModule());
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionHandler.java
similarity index 71%
rename from src/main/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheHandler.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionHandler.java
index fe8dc3a..89229fc 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionHandler.java
@@ -12,24 +12,27 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.cache;
 
 import com.google.common.cache.RemovalNotification;
 import com.google.gerrit.server.cache.CacheRemovalListener;
 import com.google.inject.Inject;
 
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Context;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
+
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.regex.Pattern;
 
-class EvictCacheHandler<K, V> implements CacheRemovalListener<K, V> {
+class CacheEvictionHandler<K, V> implements CacheRemovalListener<K, V> {
   private final ScheduledThreadPoolExecutor executor;
-  private final RestSession restSession;
+  private final Forwarder forwarder;
   private final Pattern pattern;
 
   @Inject
-  EvictCacheHandler(RestSession restSession,
-      @EvictCacheExecutor ScheduledThreadPoolExecutor executor) {
-    this.restSession = restSession;
+  CacheEvictionHandler(Forwarder forwarder,
+      @CacheExecutor ScheduledThreadPoolExecutor executor) {
+    this.forwarder = forwarder;
     this.executor = executor;
     pattern = Pattern.compile(
         "^accounts.*|^groups.*|ldap_groups|ldap_usernames|^project.*|sshkeys|web_sessions");
@@ -40,8 +43,7 @@
       RemovalNotification<K, V> notification) {
     if (!Context.isForwardedEvent() && !notification.wasEvicted()
         && isSynchronized(cacheName)) {
-      executor.execute(
-          new EvictCacheTask(pluginName, cacheName, notification.getKey()));
+      executor.execute(new CacheEvictionTask(cacheName, notification.getKey()));
     }
   }
 
@@ -49,20 +51,18 @@
     return pattern.matcher(cacheName).matches();
   }
 
-  class EvictCacheTask implements Runnable {
-    private String pluginName;
+  class CacheEvictionTask implements Runnable {
     private String cacheName;
     private Object key;
 
-    EvictCacheTask(String pluginName, String cacheName, Object key) {
-      this.pluginName = pluginName;
+    CacheEvictionTask(String cacheName, Object key) {
       this.cacheName = cacheName;
       this.key = key;
     }
 
     @Override
     public void run() {
-      restSession.evict(pluginName, cacheName, key);
+      forwarder.evict(cacheName, key);
     }
 
     @Override
diff --git a/src/main/java/com/ericsson/gerrit/plugins/evictcache/SyncUrl.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheExecutor.java
similarity index 89%
copy from src/main/java/com/ericsson/gerrit/plugins/evictcache/SyncUrl.java
copy to src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheExecutor.java
index 14c2880..bd3dd40 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/evictcache/SyncUrl.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheExecutor.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.cache;
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
@@ -22,5 +22,5 @@
 
 @Retention(RUNTIME)
 @BindingAnnotation
-@interface SyncUrl {
+@interface CacheExecutor {
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheExecutorProvider.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheExecutorProvider.java
similarity index 78%
copy from src/main/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheExecutorProvider.java
copy to src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheExecutorProvider.java
index e1de403..c427d17 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheExecutorProvider.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheExecutorProvider.java
@@ -12,28 +12,28 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.cache;
 
-import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
+
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 
 @Singleton
-class EvictCacheExecutorProvider
+class CacheExecutorProvider
     implements Provider<ScheduledThreadPoolExecutor>, LifecycleListener {
   private WorkQueue.Executor executor;
 
   @Inject
-  EvictCacheExecutorProvider(WorkQueue workQueue,
-      @PluginName String pluginName,
+  CacheExecutorProvider(WorkQueue workQueue,
       Configuration config) {
-    executor = workQueue.createQueue(config.getThreadPoolSize(),
-        "Evict cache [" + pluginName + " plugin]");
+    executor = workQueue.createQueue(config.getCacheThreadPoolSize(),
+        "Forward-cache-eviction-event");
   }
 
   @Override
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheModule.java
new file mode 100644
index 0000000..671264b
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheModule.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2015 Ericsson
+//
+// 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 com.ericsson.gerrit.plugins.highavailability.cache;
+
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.server.cache.CacheRemovalListener;
+
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+
+public class CacheModule extends LifecycleModule {
+
+  @Override
+  protected void configure() {
+    bind(ScheduledThreadPoolExecutor.class)
+        .annotatedWith(CacheExecutor.class)
+        .toProvider(CacheExecutorProvider.class);
+    listener().to(CacheExecutorProvider.class);
+    DynamicSet.bind(binder(), CacheRemovalListener.class).to(
+        CacheEvictionHandler.class);
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/Constants.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/Constants.java
new file mode 100644
index 0000000..12f92bd
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/Constants.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2015 Ericsson
+//
+// 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 com.ericsson.gerrit.plugins.highavailability.cache;
+
+public final class Constants {
+
+  public static final String PROJECT_LIST = "project_list";
+  public static final String ACCOUNTS = "accounts";
+  public static final String GROUPS = "groups";
+  public static final String GROUPS_BYINCLUDE = "groups_byinclude";
+  public static final String GROUPS_MEMBERS = "groups_members";
+  public static final String PROJECTS = "projects";
+
+  private Constants() {
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/evictcache/SyncUrl.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventExecutor.java
similarity index 89%
rename from src/main/java/com/ericsson/gerrit/plugins/evictcache/SyncUrl.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventExecutor.java
index 14c2880..b204220 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/evictcache/SyncUrl.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventExecutor.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.event;
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
@@ -22,5 +22,5 @@
 
 @Retention(RUNTIME)
 @BindingAnnotation
-@interface SyncUrl {
+@interface EventExecutor {
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheExecutorProvider.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventExecutorProvider.java
similarity index 78%
rename from src/main/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheExecutorProvider.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventExecutorProvider.java
index e1de403..6a6c3c3 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheExecutorProvider.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventExecutorProvider.java
@@ -12,28 +12,28 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.event;
 
-import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
+
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 
 @Singleton
-class EvictCacheExecutorProvider
+class EventExecutorProvider
     implements Provider<ScheduledThreadPoolExecutor>, LifecycleListener {
   private WorkQueue.Executor executor;
 
   @Inject
-  EvictCacheExecutorProvider(WorkQueue workQueue,
-      @PluginName String pluginName,
+  EventExecutorProvider(WorkQueue workQueue,
       Configuration config) {
-    executor = workQueue.createQueue(config.getThreadPoolSize(),
-        "Evict cache [" + pluginName + " plugin]");
+    executor = workQueue.createQueue(config.getEventThreadPoolSize(),
+        "Forward-stream-event");
   }
 
   @Override
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandler.java
new file mode 100644
index 0000000..c4716d3
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandler.java
@@ -0,0 +1,67 @@
+// Copyright (C) 2015 Ericsson
+//
+// 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 com.ericsson.gerrit.plugins.highavailability.event;
+
+import com.google.gerrit.common.EventListener;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.ProjectEvent;
+import com.google.inject.Inject;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Context;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
+
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+
+class EventHandler implements EventListener {
+  private final ScheduledThreadPoolExecutor executor;
+  private final Forwarder forwarder;
+  private final String pluginName;
+
+  @Inject
+  EventHandler(Forwarder forwarder,
+      @EventExecutor ScheduledThreadPoolExecutor executor,
+      @PluginName String pluginName) {
+    this.forwarder = forwarder;
+    this.executor = executor;
+    this.pluginName = pluginName;
+  }
+
+  @Override
+  public void onEvent(Event event) {
+    if (!Context.isForwardedEvent() && event instanceof ProjectEvent) {
+      executor.execute(new EventTask(event));
+    }
+  }
+
+  class EventTask implements Runnable {
+    private Event event;
+
+    EventTask(Event event) {
+      this.event = event;
+    }
+
+    @Override
+    public void run() {
+      forwarder.send(event);
+    }
+
+    @Override
+    public String toString() {
+      return String.format("[%s] Send event '%s' to target instance",
+          pluginName, event.type);
+    }
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventModule.java
new file mode 100644
index 0000000..76b68f2
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventModule.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2015 Ericsson
+//
+// 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 com.ericsson.gerrit.plugins.highavailability.event;
+
+import com.google.gerrit.common.EventDispatcher;
+import com.google.gerrit.common.EventListener;
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.lifecycle.LifecycleModule;
+
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+
+public class EventModule extends LifecycleModule {
+
+  @Override
+  protected void configure() {
+    bind(ScheduledThreadPoolExecutor.class)
+        .annotatedWith(EventExecutor.class)
+        .toProvider(EventExecutorProvider.class);
+    listener().to(EventExecutorProvider.class);
+    DynamicSet.bind(binder(), EventListener.class).to(EventHandler.class);
+    DynamicItem.bind(binder(), EventDispatcher.class).to(ForwardedAwareEventBroker.class);
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/ForwardedAwareEventBroker.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/ForwardedAwareEventBroker.java
new file mode 100644
index 0000000..e099343
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/ForwardedAwareEventBroker.java
@@ -0,0 +1,48 @@
+// Copyright (C) 2016 Ericsson
+//
+// 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 com.ericsson.gerrit.plugins.highavailability.event;
+
+import com.google.gerrit.common.EventBroker;
+import com.google.gerrit.common.EventListener;
+import com.google.gerrit.common.UserScopedEventListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.notedb.ChangeNotes.Factory;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Context;
+
+class ForwardedAwareEventBroker extends EventBroker {
+
+  @Inject
+  ForwardedAwareEventBroker(DynamicSet<UserScopedEventListener> listeners,
+      DynamicSet<EventListener> unrestrictedListeners,
+      ProjectCache projectCache,
+      Factory notesFactory,
+      Provider<ReviewDb> dbProvider) {
+    super(listeners, unrestrictedListeners, projectCache, notesFactory,
+        dbProvider);
+  }
+
+  @Override
+  protected void fireEventForUnrestrictedListeners(Event event) {
+    if (!Context.isForwardedEvent()) {
+      super.fireEventForUnrestrictedListeners(event);
+    }
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/evictcache/Context.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Context.java
similarity index 78%
rename from src/main/java/com/ericsson/gerrit/plugins/evictcache/Context.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Context.java
index 0b0519b..3651e73 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/evictcache/Context.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Context.java
@@ -12,12 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
 /**
  * Allows to tag a forwarded event to avoid infinitely looping events.
  */
-class Context {
+public class Context {
   private static final ThreadLocal<Boolean> FORWARDED_EVENT =
       new ThreadLocal<Boolean>() {
         @Override
@@ -29,15 +29,15 @@
   private Context() {
   }
 
-  static Boolean isForwardedEvent() {
+  public static Boolean isForwardedEvent() {
     return FORWARDED_EVENT.get();
   }
 
-  static void setForwardedEvent() {
-    FORWARDED_EVENT.set(true);
+  public static void setForwardedEvent(Boolean b) {
+    FORWARDED_EVENT.set(b);
   }
 
-  static void unsetForwardedEvent() {
+  public static void unsetForwardedEvent() {
     FORWARDED_EVENT.remove();
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java
new file mode 100644
index 0000000..0d1a0bb
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java
@@ -0,0 +1,56 @@
+// Copyright (C) 2017 Ericsson
+//
+// 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 com.ericsson.gerrit.plugins.highavailability.forwarder;
+
+import com.google.gerrit.server.events.Event;
+
+/**
+ * Forward indexing, stream events and cache evictions to the other master
+ */
+public interface Forwarder {
+
+  /**
+   * Forward a change indexing event to the other master.
+   *
+   * @param changeId the change to index.
+   * @return true if successful, otherwise false.
+   */
+  boolean indexChange(int changeId);
+
+  /**
+   * Forward a delete change from index event to the other master.
+   *
+   * @param changeId the change to remove from the index.
+   * @return rue if successful, otherwise false.
+   */
+  boolean deleteChangeFromIndex(int changeId);
+
+  /**
+   * Forward a stream event to the other master.
+   *
+   * @param event the event to forward.
+   * @return true if successful, otherwise false.
+   */
+  boolean send(Event event);
+
+  /**
+   * Forward a cache eviction event to the other master.
+   *
+   * @param cacheName the name of the cache to evict an entry from.
+   * @param key the key identifying the entry to evict from the cache.
+   * @return true if successful, otherwise false.
+   */
+  boolean evict(String cacheName, Object key);
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServlet.java
similarity index 82%
rename from src/main/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheRestApiServlet.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServlet.java
index bb4f0ba..ed5e593 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheRestApiServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServlet.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
 import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
@@ -23,6 +23,9 @@
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
+import com.ericsson.gerrit.plugins.highavailability.cache.Constants;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Context;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -35,18 +38,17 @@
 import javax.servlet.http.HttpServletResponse;
 
 @Singleton
-class EvictCacheRestApiServlet extends HttpServlet {
+class CacheRestApiServlet extends HttpServlet {
   private static final int CACHENAME_INDEX = 1;
   private static final long serialVersionUID = -1L;
-  private static final String SOURCE_NAME = "gerrit";
-  private static final String PROJECT_LIST = "project_list";
+  private static final String GERRIT = "gerrit";
   private static final Logger logger =
-      LoggerFactory.getLogger(EvictCacheRestApiServlet.class);
+      LoggerFactory.getLogger(CacheRestApiServlet.class);
 
   private final DynamicMap<Cache<?, ?>> cacheMap;
 
   @Inject
-  EvictCacheRestApiServlet(DynamicMap<Cache<?, ?>> cacheMap) {
+  CacheRestApiServlet(DynamicMap<Cache<?, ?>> cacheMap) {
     this.cacheMap = cacheMap;
   }
 
@@ -60,8 +62,8 @@
       String cacheName = params.get(CACHENAME_INDEX);
       String json = req.getReader().readLine();
       Object key = GsonParser.fromJson(cacheName, json);
-      Cache<?, ?> cache = cacheMap.get(SOURCE_NAME, cacheName);
-      Context.setForwardedEvent();
+      Cache<?, ?> cache = cacheMap.get(GERRIT, cacheName);
+      Context.setForwardedEvent(true);
       evictCache(cache, cacheName, key);
       rsp.setStatus(SC_NO_CONTENT);
     } catch (IOException e) {
@@ -82,7 +84,7 @@
   }
 
   private void evictCache(Cache<?, ?> cache, String cacheName, Object key) {
-    if (PROJECT_LIST.equals(cacheName)) {
+    if (Constants.PROJECT_LIST.equals(cacheName)) {
       // One key is holding the list of projects
       cache.invalidateAll();
     } else {
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java
new file mode 100644
index 0000000..eb8e53e
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java
@@ -0,0 +1,104 @@
+// Copyright (C) 2015 Ericsson
+//
+// 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 com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
+
+import static com.google.common.net.MediaType.JSON_UTF_8;
+import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
+import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
+import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
+import static javax.servlet.http.HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE;
+
+import com.google.common.base.Supplier;
+import com.google.common.io.CharStreams;
+import com.google.common.net.MediaType;
+import com.google.gerrit.common.EventDispatcher;
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.EventDeserializer;
+import com.google.gerrit.server.events.SupplierDeserializer;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Context;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@Singleton
+class EventRestApiServlet extends HttpServlet {
+  private static final long serialVersionUID = -1L;
+  private static final Logger logger = LoggerFactory
+      .getLogger(EventRestApiServlet.class);
+
+  private final EventDispatcher dispatcher;
+
+  @Inject
+  EventRestApiServlet(EventDispatcher dispatcher) {
+    this.dispatcher = dispatcher;
+  }
+
+  @Override
+  protected void doPost(HttpServletRequest req, HttpServletResponse rsp)
+      throws IOException, ServletException {
+    rsp.setContentType("text/plain");
+    rsp.setCharacterEncoding("UTF-8");
+    try {
+      Context.setForwardedEvent(true);
+      if (!MediaType.parse(req.getContentType()).is(JSON_UTF_8)) {
+        sendError(rsp, SC_UNSUPPORTED_MEDIA_TYPE,
+            "Expecting " + JSON_UTF_8.toString() + " content type");
+        return;
+      }
+      Event event = getEventFromRequest(req);
+      dispatcher.postEvent(event);
+      rsp.setStatus(SC_NO_CONTENT);
+    } catch (OrmException e) {
+      logger.debug("Error trying to find a change ", e);
+      sendError(rsp, SC_NOT_FOUND, "Change not found\n");
+    } catch (IOException e) {
+      logger.error("Unable to re-trigger event", e);
+      sendError(rsp, SC_BAD_REQUEST, e.getMessage());
+    } finally {
+      Context.unsetForwardedEvent();
+    }
+  }
+
+  private Event getEventFromRequest(HttpServletRequest req) throws IOException {
+    String jsonEvent = CharStreams.toString(req.getReader());
+    Gson gson = new GsonBuilder()
+        .registerTypeAdapter(Event.class, new EventDeserializer())
+        .registerTypeAdapter(Supplier.class, new SupplierDeserializer())
+        .create();
+    return gson.fromJson(jsonEvent, Event.class);
+  }
+
+  private static void sendError(HttpServletResponse rsp, int statusCode,
+      String message) {
+    try {
+      rsp.sendError(statusCode, message);
+    } catch (IOException e) {
+      logger.error("Failed to send error messsage: " + e.getMessage(), e);
+    }
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/evictcache/SyncUrl.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ForwardUrl.java
similarity index 88%
copy from src/main/java/com/ericsson/gerrit/plugins/evictcache/SyncUrl.java
copy to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ForwardUrl.java
index 14c2880..72ab757 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/evictcache/SyncUrl.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ForwardUrl.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
@@ -22,5 +22,5 @@
 
 @Retention(RUNTIME)
 @BindingAnnotation
-@interface SyncUrl {
+@interface ForwardUrl {
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/evictcache/GsonParser.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonParser.java
similarity index 77%
rename from src/main/java/com/ericsson/gerrit/plugins/evictcache/GsonParser.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonParser.java
index d752c06..650bc28 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/evictcache/GsonParser.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonParser.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import com.google.common.base.Strings;
 import com.google.gerrit.reviewdb.client.Account;
@@ -20,12 +20,9 @@
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 
+import com.ericsson.gerrit.plugins.highavailability.cache.Constants;
+
 final class GsonParser {
-  private static final String ACCOUNTS = "accounts";
-  private static final String GROUPS = "groups";
-  private static final String GROUPS_BYINCLUDE = "groups_byinclude";
-  private static final String GROUPS_MEMBERS = "groups_members";
-  private static final String PROJECT_LIST = "project_list";
 
   private GsonParser() {
   }
@@ -35,19 +32,19 @@
     Object key;
     // Need to add a case for 'adv_bases'
     switch (cacheName) {
-      case ACCOUNTS:
+      case Constants.ACCOUNTS:
         key = gson.fromJson(Strings.nullToEmpty(json).trim(), Account.Id.class);
         break;
-      case GROUPS:
+      case Constants.GROUPS:
         key = gson.fromJson(Strings.nullToEmpty(json).trim(),
             AccountGroup.Id.class);
         break;
-      case GROUPS_BYINCLUDE:
-      case GROUPS_MEMBERS:
+      case Constants.GROUPS_BYINCLUDE:
+      case Constants.GROUPS_MEMBERS:
         key = gson.fromJson(Strings.nullToEmpty(json).trim(),
             AccountGroup.UUID.class);
         break;
-      case PROJECT_LIST:
+      case Constants.PROJECT_LIST:
         key = gson.fromJson(Strings.nullToEmpty(json), Object.class);
         break;
       default:
@@ -61,17 +58,17 @@
     String json;
     // Need to add a case for 'adv_bases'
     switch (cacheName) {
-      case ACCOUNTS:
+      case Constants.ACCOUNTS:
         json = gson.toJson(key, Account.Id.class);
         break;
-      case GROUPS:
+      case Constants.GROUPS:
         json = gson.toJson(key, AccountGroup.Id.class);
         break;
-      case GROUPS_BYINCLUDE:
-      case GROUPS_MEMBERS:
+      case Constants.GROUPS_BYINCLUDE:
+      case Constants.GROUPS_MEMBERS:
         json = gson.toJson(key, AccountGroup.UUID.class);
         break;
-      case PROJECT_LIST:
+      case Constants.PROJECT_LIST:
       default:
         json = gson.toJson(key);
     }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/evictcache/HttpClientProvider.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpClientProvider.java
similarity index 91%
rename from src/main/java/com/ericsson/gerrit/plugins/evictcache/HttpClientProvider.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpClientProvider.java
index df42800..7e4eb28 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/evictcache/HttpClientProvider.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpClientProvider.java
@@ -12,11 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import com.google.inject.Inject;
 import com.google.inject.Provider;
-import com.google.inject.Singleton;
+
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
 
 import org.apache.http.HttpResponse;
 import org.apache.http.auth.AuthScope;
@@ -29,6 +30,7 @@
 import org.apache.http.conn.HttpClientConnectionManager;
 import org.apache.http.conn.socket.ConnectionSocketFactory;
 import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
 import org.apache.http.impl.client.BasicCredentialsProvider;
 import org.apache.http.impl.client.CloseableHttpClient;
@@ -44,26 +46,22 @@
 import java.security.NoSuchAlgorithmException;
 import java.security.cert.X509Certificate;
 
-import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLSession;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.X509TrustManager;
 
 /**
  * Provides an HTTP client with SSL capabilities.
  */
-@Singleton
 class HttpClientProvider implements Provider<CloseableHttpClient> {
-  private static final Logger log =
-      LoggerFactory.getLogger(HttpClientProvider.class);
+  private static final Logger log = LoggerFactory
+      .getLogger(HttpClientProvider.class);
   private static final int CONNECTIONS_PER_ROUTE = 100;
   // Up to 2 target instances with the max number of connections per host:
   private static final int MAX_CONNECTIONS = 2 * CONNECTIONS_PER_ROUTE;
 
   private static final int ERROR_CODES = 500;
-
   private static final int MAX_CONNECTION_INACTIVITY = 10000;
 
   private final Configuration cfg;
@@ -138,7 +136,7 @@
   }
 
   private void logRetry(String cause) {
-    log.warn("Retrying request to '" + cfg.getUrl() + "' Cause: " + cause);
+    log.debug("Retrying request to '" + cfg.getUrl() + "' Cause: " + cause);
   }
 
   private HttpClientConnectionManager customConnectionManager() {
@@ -155,7 +153,7 @@
 
   private SSLConnectionSocketFactory buildSslSocketFactory() {
     return new SSLConnectionSocketFactory(buildSslContext(),
-        new DummyHostnameVerifier());
+        NoopHostnameVerifier.INSTANCE);
   }
 
   private SSLContext buildSslContext() {
@@ -195,12 +193,4 @@
       // no check
     }
   }
-
-  private static class DummyHostnameVerifier implements HostnameVerifier {
-    @Override
-    public boolean verify(String hostname, SSLSession session) {
-      // always accept
-      return true;
-    }
-  }
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/evictcache/CacheResponseHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpResponseHandler.java
similarity index 76%
rename from src/main/java/com/ericsson/gerrit/plugins/evictcache/CacheResponseHandler.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpResponseHandler.java
index 768ee72..ad98f2e 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/evictcache/CacheResponseHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpResponseHandler.java
@@ -12,11 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
 
-import com.ericsson.gerrit.plugins.evictcache.CacheResponseHandler.CacheResult;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.HttpResponseHandler.HttpResult;
 
 import org.apache.http.HttpEntity;
 import org.apache.http.HttpResponse;
@@ -27,13 +27,13 @@
 
 import java.io.IOException;
 
-class CacheResponseHandler implements ResponseHandler<CacheResult> {
+class HttpResponseHandler implements ResponseHandler<HttpResult> {
 
-  static class CacheResult {
+  static class HttpResult {
     private boolean successful;
     private String message;
 
-    CacheResult(boolean successful, String message) {
+    HttpResult(boolean successful, String message) {
       this.successful = successful;
       this.message = message;
     }
@@ -48,11 +48,11 @@
   }
 
   private static final Logger log =
-      LoggerFactory.getLogger(CacheResponseHandler.class);
+      LoggerFactory.getLogger(HttpResponseHandler.class);
 
   @Override
-  public CacheResult handleResponse(HttpResponse response) {
-    return new CacheResult(isSuccessful(response), parseResponse(response));
+  public HttpResult handleResponse(HttpResponse response) {
+    return new HttpResult(isSuccessful(response), parseResponse(response));
   }
 
   private boolean isSuccessful(HttpResponse response) {
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java
new file mode 100644
index 0000000..574dd82
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java
@@ -0,0 +1,59 @@
+// Copyright (C) 2015 Ericsson
+//
+// 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 com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
+
+import com.google.common.base.Strings;
+import com.google.common.net.MediaType;
+import com.google.inject.Inject;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.HttpResponseHandler.HttpResult;
+
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+class HttpSession {
+  private final CloseableHttpClient httpClient;
+  private final String url;
+
+  @Inject
+  HttpSession(CloseableHttpClient httpClient,
+      @ForwardUrl String url) {
+    this.httpClient = httpClient;
+    this.url = url;
+  }
+
+  HttpResult post(String endpoint) throws IOException {
+    return post(endpoint, null);
+  }
+
+  HttpResult post(String endpoint, String content) throws IOException {
+    HttpPost post = new HttpPost(url + endpoint);
+    if (!Strings.isNullOrEmpty(content)) {
+      post.addHeader("Content-Type", MediaType.JSON_UTF_8.toString());
+      post.setEntity(new StringEntity(content, StandardCharsets.UTF_8));
+    }
+    return httpClient.execute(post, new HttpResponseHandler());
+  }
+
+  HttpResult delete(String endpoint) throws IOException {
+    return httpClient.execute(new HttpDelete(url + endpoint),
+        new HttpResponseHandler());
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexRestApiServlet.java
new file mode 100644
index 0000000..0a6761e
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexRestApiServlet.java
@@ -0,0 +1,149 @@
+// Copyright (C) 2015 Ericsson
+//
+// 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 com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
+
+import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
+import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
+import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
+
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.index.change.ChangeIndexer;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Context;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@Singleton
+class IndexRestApiServlet extends HttpServlet {
+  private static final long serialVersionUID = -1L;
+  private static final Logger logger =
+      LoggerFactory.getLogger(IndexRestApiServlet.class);
+  private static final Map<Change.Id, AtomicInteger> changeIdLocks =
+      new HashMap<>();
+
+  private final ChangeIndexer indexer;
+  private final SchemaFactory<ReviewDb> schemaFactory;
+
+  @Inject
+  IndexRestApiServlet(ChangeIndexer indexer,
+      SchemaFactory<ReviewDb> schemaFactory) {
+    this.indexer = indexer;
+    this.schemaFactory = schemaFactory;
+  }
+
+  @Override
+  protected void doPost(HttpServletRequest req, HttpServletResponse rsp)
+      throws IOException, ServletException {
+    process(req, rsp, "index");
+  }
+
+  @Override
+  protected void doDelete(HttpServletRequest req, HttpServletResponse rsp)
+      throws IOException, ServletException {
+    process(req, rsp, "delete");
+  }
+
+  private void process(HttpServletRequest req, HttpServletResponse rsp,
+      String operation) {
+    rsp.setContentType("text/plain");
+    rsp.setCharacterEncoding("UTF-8");
+    String path = req.getPathInfo();
+    String changeId = path.substring(path.lastIndexOf('/') + 1);
+    Change.Id id = Change.Id.parse(changeId);
+    try {
+      Context.setForwardedEvent(true);
+      index(id, operation);
+      rsp.setStatus(SC_NO_CONTENT);
+    } catch (IOException e) {
+      sendError(rsp,SC_CONFLICT, e.getMessage());
+      logger.error("Unable to update index", e);
+    } catch (OrmException e) {
+      String msg = "Error trying to find a change \n";
+      sendError(rsp,SC_NOT_FOUND, msg);
+      logger.debug(msg, e);
+    } finally {
+      Context.unsetForwardedEvent();
+    }
+  }
+
+  private static void sendError(HttpServletResponse rsp, int statusCode,
+      String message) {
+    try {
+      rsp.sendError(statusCode, message);
+    } catch (IOException e) {
+      logger.error("Failed to send error messsage: " + e.getMessage(), e);
+    }
+  }
+
+  private void index(Change.Id id, String operation)
+      throws IOException, OrmException {
+    AtomicInteger changeIdLock = getAndIncrementChangeIdLock(id);
+    synchronized (changeIdLock) {
+      if ("index".equals(operation)) {
+        try (ReviewDb db = schemaFactory.open()) {
+          Change change = db.changes().get(id);
+          if (change == null) {
+            indexer.delete(id);
+            return;
+          }
+          indexer.index(db, change);
+        }
+        logger.debug("Change {} successfully indexed", id);
+      }
+      if ("delete".equals(operation)) {
+        indexer.delete(id);
+        logger.debug("Change {} successfully deleted from index", id);
+      }
+    }
+    if (changeIdLock.decrementAndGet() == 0) {
+      removeChangeIdLock(id);
+    }
+  }
+
+  private AtomicInteger getAndIncrementChangeIdLock(Change.Id id) {
+    synchronized (changeIdLocks) {
+      AtomicInteger changeIdLock = changeIdLocks.get(id);
+      if (changeIdLock == null) {
+        changeIdLock = new AtomicInteger(1);
+        changeIdLocks.put(id, changeIdLock);
+      } else {
+        changeIdLock.incrementAndGet();
+      }
+      return changeIdLock;
+    }
+  }
+
+  private void removeChangeIdLock(Change.Id id) {
+    synchronized (changeIdLocks) {
+      changeIdLocks.remove(id);
+    }
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java
new file mode 100644
index 0000000..75481b3
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java
@@ -0,0 +1,114 @@
+// Copyright (C) 2015 Ericsson
+//
+// 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 com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Supplier;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.SupplierSerializer;
+import com.google.gson.GsonBuilder;
+import com.google.inject.Inject;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.HttpResponseHandler.HttpResult;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+class RestForwarder implements Forwarder {
+  private static final Logger log =
+      LoggerFactory.getLogger(RestForwarder.class);
+
+  private final HttpSession httpSession;
+  private final String pluginName;
+
+  @Inject
+  RestForwarder(HttpSession httpClient,
+      @PluginName String pluginName) {
+    this.httpSession = httpClient;
+    this.pluginName = pluginName;
+  }
+
+  @Override
+  public boolean indexChange(int changeId) {
+    try {
+      HttpResult result = httpSession.post(buildEndpoint(changeId));
+      if (result.isSuccessful()) {
+        return true;
+      }
+      log.error("Unable to index change {}. Cause: {}", changeId,
+          result.getMessage());
+    } catch (IOException e) {
+      log.error("Error trying to index change " + changeId, e);
+    }
+    return false;
+  }
+
+  @Override
+  public boolean deleteChangeFromIndex(int changeId) {
+    try {
+      HttpResult result = httpSession.delete(buildEndpoint(changeId));
+      if (result.isSuccessful()) {
+        return true;
+      }
+      log.error("Unable to delete from index change {}. Cause: {}", changeId,
+          result.getMessage());
+    } catch (IOException e) {
+      log.error("Error trying to delete from index change " + changeId, e);
+    }
+    return false;
+  }
+
+  private String buildEndpoint(int changeId) {
+    return Joiner.on("/").join("/plugins", pluginName, "index", changeId);
+  }
+
+  @Override
+  public boolean send(Event event) {
+    String serializedEvent = new GsonBuilder()
+        .registerTypeAdapter(Supplier.class, new SupplierSerializer()).create()
+        .toJson(event);
+    try {
+      HttpResult result =
+          httpSession.post(Joiner.on("/").join("/plugins", pluginName, "event"),
+              serializedEvent);
+      if (result.isSuccessful()) {
+        return true;
+      }
+      log.error(
+          "Unable to send event '" + event.type + "' " + result.getMessage());
+    } catch (IOException e) {
+      log.error("Error trying to send event " + event.type, e);
+    }
+    return false;
+  }
+
+  @Override
+  public boolean evict(String cacheName, Object key) {
+    try {
+      String json = GsonParser.toJson(cacheName, key);
+      return httpSession
+          .post(Joiner.on("/").join("/plugins", pluginName, "cache", cacheName),
+              json)
+          .isSuccessful();
+    } catch (IOException e) {
+      log.error("Error trying to evict for cache " + cacheName, e);
+      return false;
+    }
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderModule.java
new file mode 100644
index 0000000..e9a57f9
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderModule.java
@@ -0,0 +1,41 @@
+// Copyright (C) 2017 Ericsson
+//
+// 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 com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
+
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.inject.Provides;
+import com.google.inject.Scopes;
+
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
+
+import org.apache.http.impl.client.CloseableHttpClient;
+
+public class RestForwarderModule extends LifecycleModule {
+
+  @Override
+  protected void configure() {
+    bind(CloseableHttpClient.class).toProvider(HttpClientProvider.class)
+        .in(Scopes.SINGLETON);
+    bind(HttpSession.class);
+    bind(Forwarder.class).to(RestForwarder.class);
+  }
+
+  @Provides
+  @ForwardUrl
+  String forwardUrl(Configuration config) {
+    return config.getUrl();
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/evictcache/HttpModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderServletModule.java
similarity index 66%
copy from src/main/java/com/ericsson/gerrit/plugins/evictcache/HttpModule.java
copy to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderServletModule.java
index 27cf205..71b9bf0 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/evictcache/HttpModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderServletModule.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Ericsson
+// Copyright (C) 2017 Ericsson
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,13 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import com.google.gerrit.httpd.plugins.HttpPluginModule;
 
-class HttpModule extends HttpPluginModule {
+public class RestForwarderServletModule extends HttpPluginModule {
   @Override
   protected void configureServlets() {
-    serve("/gerrit/evict/*").with(EvictCacheRestApiServlet.class);
+    serveRegex("/index/\\d+$").with(IndexRestApiServlet.class);
+    serve("/event").with(EventRestApiServlet.class);
+    serve("/cache/*").with(CacheRestApiServlet.class);
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java
new file mode 100644
index 0000000..36a5c82
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java
@@ -0,0 +1,104 @@
+// Copyright (C) 2015 Ericsson
+//
+// 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 com.ericsson.gerrit.plugins.highavailability.index;
+
+import com.google.common.base.Objects;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.events.ChangeIndexedListener;
+import com.google.inject.Inject;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Context;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+
+class IndexEventHandler implements ChangeIndexedListener {
+  private final Executor executor;
+  private final Forwarder forwarder;
+  private final String pluginName;
+  private final Set<IndexTask> queuedTasks = Collections
+      .newSetFromMap(new ConcurrentHashMap<IndexTask, Boolean>());
+
+  @Inject
+  IndexEventHandler(@IndexExecutor Executor executor,
+      @PluginName String pluginName,
+      Forwarder forwarder) {
+    this.forwarder = forwarder;
+    this.executor = executor;
+    this.pluginName = pluginName;
+  }
+
+  @Override
+  public void onChangeIndexed(int id) {
+    executeIndexTask(id, false);
+  }
+
+  @Override
+  public void onChangeDeleted(int id) {
+    executeIndexTask(id, true);
+  }
+
+  private void executeIndexTask(int id, boolean deleted) {
+    if (!Context.isForwardedEvent()) {
+      IndexTask indexTask = new IndexTask(id, deleted);
+      if (queuedTasks.add(indexTask)) {
+        executor.execute(indexTask);
+      }
+    }
+  }
+
+  class IndexTask implements Runnable {
+    private int changeId;
+    private boolean deleted;
+
+    IndexTask(int changeId, boolean deleted) {
+      this.changeId = changeId;
+      this.deleted = deleted;
+    }
+
+    @Override
+    public void run() {
+      queuedTasks.remove(this);
+      if (deleted) {
+        forwarder.deleteChangeFromIndex(changeId);
+      } else {
+        forwarder.indexChange(changeId);
+      }
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(changeId, deleted);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (!(obj instanceof IndexTask)) {
+        return false;
+      }
+      IndexTask other = (IndexTask) obj;
+      return changeId == other.changeId && deleted == other.deleted;
+    }
+
+    @Override
+    public String toString() {
+      return String.format("[%s] Index change %s in target instance",
+          pluginName, changeId);
+    }
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/evictcache/SyncUrl.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexExecutor.java
similarity index 89%
copy from src/main/java/com/ericsson/gerrit/plugins/evictcache/SyncUrl.java
copy to src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexExecutor.java
index 14c2880..e6c20bd 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/evictcache/SyncUrl.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexExecutor.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.index;
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
@@ -22,5 +22,5 @@
 
 @Retention(RUNTIME)
 @BindingAnnotation
-@interface SyncUrl {
+@interface IndexExecutor {
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheExecutorProvider.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexExecutorProvider.java
similarity index 65%
copy from src/main/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheExecutorProvider.java
copy to src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexExecutorProvider.java
index e1de403..73aa3be 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheExecutorProvider.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexExecutorProvider.java
@@ -12,33 +12,32 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.index;
 
-import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
-import java.util.concurrent.ScheduledThreadPoolExecutor;
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
+
+import java.util.concurrent.Executor;
 
 @Singleton
-class EvictCacheExecutorProvider
-    implements Provider<ScheduledThreadPoolExecutor>, LifecycleListener {
+class IndexExecutorProvider implements Provider<Executor>,
+    LifecycleListener {
   private WorkQueue.Executor executor;
 
   @Inject
-  EvictCacheExecutorProvider(WorkQueue workQueue,
-      @PluginName String pluginName,
-      Configuration config) {
-    executor = workQueue.createQueue(config.getThreadPoolSize(),
-        "Evict cache [" + pluginName + " plugin]");
+  IndexExecutorProvider(WorkQueue workQueue, Configuration config) {
+    executor = workQueue.createQueue(config.getIndexThreadPoolSize(),
+        "Forward-index-event");
   }
 
   @Override
   public void start() {
-    // do nothing
+    //do nothing
   }
 
   @Override
@@ -49,7 +48,7 @@
   }
 
   @Override
-  public ScheduledThreadPoolExecutor get() {
+  public Executor get() {
     return executor;
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexModule.java
new file mode 100644
index 0000000..7e8b116
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexModule.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 Ericsson
+//
+// 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 com.ericsson.gerrit.plugins.highavailability.index;
+
+import com.google.gerrit.extensions.events.ChangeIndexedListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.lifecycle.LifecycleModule;
+
+import java.util.concurrent.Executor;
+
+public class IndexModule extends LifecycleModule {
+
+  @Override
+  protected void configure() {
+    bind(Executor.class)
+        .annotatedWith(IndexExecutor.class)
+        .toProvider(IndexExecutorProvider.class);
+    listener().to(IndexExecutorProvider.class);
+    DynamicSet.bind(binder(), ChangeIndexedListener.class).to(
+        IndexEventHandler.class);
+  }
+}
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index a5adccc..f0f371b 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -1,10 +1,22 @@
-The @PLUGIN@ plugin allows to synchronize the eviction of caches between two
-Gerrit instances sharing the same repositories and database.
+The @PLUGIN@ plugin allows to synchronize eviction of caches, secondary indexes
+and stream events between two Gerrit instances sharing the same git repositories
+and database. The plugin needs to be installed in both instances.
 
-The plugin needs to be installed in both instances and every time a cache
-eviction occurs in one of the instances, the other instance's cache is updated.
+Every time a cache eviction occurs in one of the instances, the other instance's
+cache is updated.
+
 This way, both caches are kept synchronized.
 
-For this synchronization to work, http must be enabled in both instances and the
-plugin must be configured with valid credentials. For further information, refer
-to [config](config.md) documentation.
+Every time the secondary index is modified in one of the instances, i.e., a
+change is added, updated or removed from the index, the other instance index is
+updated accordingly. This way, both indexes are kept synchronized.
+
+Every time a stream event occurs in one of the instances (see [more events info]
+(https://gerrit-review.googlesource.com/Documentation/cmd-stream-events.html#events)),
+the event is forwarded to the other instance which re-plays it. This way, the
+output of the stream-events command is the same, no matter what instance a
+client is connected to.
+
+For this to work, http must be enabled in both instances and the plugin
+must be configured with valid credentials. For further information, refer to
+[config](config.md) documentation.
diff --git a/src/main/resources/Documentation/build.md b/src/main/resources/Documentation/build.md
index b5b5ed7..3babed2 100644
--- a/src/main/resources/Documentation/build.md
+++ b/src/main/resources/Documentation/build.md
@@ -103,4 +103,4 @@
 
 [Back to @PLUGIN@ documentation index][index]
 
-[index]: index.html
\ No newline at end of file
+[index]: index.html
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index da544a7..e3865fd 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -1,13 +1,17 @@
 @PLUGIN@ Configuration
 =========================
 
-In order for the synchronization to work, the @PLUGIN@ plugin must be installed
-in both instances and the following fields should be specified in the
-corresponding Gerrit configuration file:
+The @PLUGIN@ plugin must be installed in both instances and the following fields
+should be specified in the corresponding Gerrit configuration file:
 
 File 'gerrit.config'
 --------------------
 
+[plugin "@PLUGIN@"]
+:  url = target_instance_url
+:  user = username
+:  password = password
+
 plugin.@PLUGIN@.url
 :   Specify the URL for the secondary (target) instance.
 
@@ -16,11 +20,11 @@
 
 plugin.@PLUGIN@.password
 :   Password to connect to the secondary (target) instance. This value can
-    also be defined in secure.config.
+     also be defined in secure.config.
 
-@PLUGIN@ plugin uses REST API calls to synchronize cache evictions in the target
-instance. It is possible to customize the parameters of the underlying http client
-doing these calls by specifying the following fields:
+@PLUGIN@ plugin uses REST API calls to keep the target instance in-sync. It
+is possible to customize the parameters of the underlying http client doing these
+calls by specifying the following fields:
 
 @PLUGIN@.connectionTimeout
 :   Maximum interval of time in milliseconds the plugin waits for a connection
@@ -32,15 +36,23 @@
     the default value is set to 5000ms.
 
 @PLUGIN@.maxTries
-:   Maximum number of times the plugin should attempt to evict the cache in the
-    target instance. Setting this value to 0 will disable retries. When not
-    specified, the default value is 5. After this number of failed tries, an error
-    is logged so that admins can flush the cache manually.
+:   Maximum number of times the plugin should attempt when calling a REST API in
+    the target instance. Setting this value to 0 will disable retries. When not
+    specified, the default value is 5. After this number of failed tries, an
+    error is logged.
 
 @PLUGIN@.retryInterval
 :   The interval of time in milliseconds between the subsequent auto-retries.
     When not specified, the default value is set to 1000ms.
 
-@PLUGIN@.threadPoolSize
-:   Maximum number of threads used to send cache evictions to the target instance.
+@PLUGIN@.indexThreadPoolSize
+:   Maximum number of threads used to send index events to the target instance.
     Defaults to 1.
+
+@PLUGIN@.eventThreadPoolSize
+:   Maximum number of threads used to send stream events to the target instance.
+    Defaults to 1.
+
+@PLUGIN@.cacheThreadPoolSize
+:   Maximum number of threads used to send cache evictions to the target instance.
+    Defaults to 1.
\ No newline at end of file
diff --git a/src/test/java/com/ericsson/gerrit/plugins/evictcache/Constants.java b/src/test/java/com/ericsson/gerrit/plugins/evictcache/Constants.java
deleted file mode 100644
index f7ba16c..0000000
--- a/src/test/java/com/ericsson/gerrit/plugins/evictcache/Constants.java
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (C) 2015 Ericsson
-//
-// 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 com.ericsson.gerrit.plugins.evictcache;
-
-final class Constants {
-
-  private Constants() {
-  }
-
-  static final String URL = "http://localhost:18888";
-  static final String ENDPOINT_BASE = "/plugins/evict-cache/gerrit/evict/";
-  static final String PROJECT_LIST = "project_list";
-  static final String ACCOUNTS = "accounts";
-  static final String GROUPS = "groups";
-  static final String GROUPS_BYINCLUDE = "groups_byinclude";
-  static final String GROUPS_MEMBERS = "groups_members";
-  static final String DEFAULT = "projects";
-
-  static final int PORT = 18888;
-}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/evictcache/ContextTest.java b/src/test/java/com/ericsson/gerrit/plugins/evictcache/ContextTest.java
deleted file mode 100644
index f7f51b6..0000000
--- a/src/test/java/com/ericsson/gerrit/plugins/evictcache/ContextTest.java
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright (C) 2015 Ericsson
-//
-// 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 com.ericsson.gerrit.plugins.evictcache;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import org.junit.Test;
-
-public class ContextTest {
-
-  @Test
-  public void testInitialValueNotNull() throws Exception {
-    assertThat(Context.isForwardedEvent()).isNotNull();
-    assertThat(Context.isForwardedEvent()).isFalse();
-  }
-
-  @Test
-  public void testSetForwardedEvent() throws Exception {
-    Context.setForwardedEvent();
-    try {
-      assertThat(Context.isForwardedEvent()).isTrue();
-    } finally {
-      Context.unsetForwardedEvent();
-    }
-  }
-
-  @Test
-  public void testUnsetForwardedEvent() throws Exception {
-    Context.setForwardedEvent();
-    Context.unsetForwardedEvent();
-    assertThat(Context.isForwardedEvent()).isFalse();
-  }
-}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/evictcache/RestSessionTest.java b/src/test/java/com/ericsson/gerrit/plugins/evictcache/RestSessionTest.java
deleted file mode 100644
index 731792b..0000000
--- a/src/test/java/com/ericsson/gerrit/plugins/evictcache/RestSessionTest.java
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright (C) 2015 Ericsson
-//
-// 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 com.ericsson.gerrit.plugins.evictcache;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import com.google.common.base.Joiner;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-
-import com.ericsson.gerrit.plugins.evictcache.CacheResponseHandler.CacheResult;
-
-import org.junit.Test;
-
-import java.io.IOException;
-
-public class RestSessionTest {
-  private static final String EVICT = "evict";
-  private static final String SOURCE_NAME = "gerrit";
-  private static final String PLUGIN_NAME = "evict-cache";
-  private static final String EMPTY_JSON = "{}";
-  private static final String EMPTY_JSON2 = "\"{}\"";
-  private static final String ID_RESPONSE = "{\"id\":0}";
-  private static final boolean OK_RESPONSE = true;
-  private static final boolean FAIL_RESPONSE = false;
-  private static final boolean THROW_EXCEPTION = true;
-  private static final boolean DO_NOT_THROW_EXCEPTION = false;
-
-  private RestSession restClient;
-
-  @Test
-  public void testEvictCacheOK() throws Exception {
-    setupMocks(Constants.DEFAULT, EMPTY_JSON2, OK_RESPONSE,
-        DO_NOT_THROW_EXCEPTION);
-    assertThat(restClient.evict(SOURCE_NAME, Constants.DEFAULT, EMPTY_JSON))
-        .isTrue();
-  }
-
-  @Test
-  public void testEvictAccountsOK() throws Exception {
-    setupMocks(Constants.ACCOUNTS, ID_RESPONSE, OK_RESPONSE,
-        DO_NOT_THROW_EXCEPTION);
-    assertThat(restClient.evict(SOURCE_NAME, Constants.ACCOUNTS,
-        mock(Account.Id.class))).isTrue();
-  }
-
-  @Test
-  public void testEvictGroupsOK() throws Exception {
-    setupMocks(Constants.GROUPS, ID_RESPONSE, OK_RESPONSE,
-        DO_NOT_THROW_EXCEPTION);
-    assertThat(restClient.evict(SOURCE_NAME, Constants.GROUPS,
-        mock(AccountGroup.Id.class))).isTrue();
-  }
-
-  @Test
-  public void testEvictGroupsByIncludeOK() throws Exception {
-    setupMocks(Constants.GROUPS_BYINCLUDE, EMPTY_JSON, OK_RESPONSE,
-        DO_NOT_THROW_EXCEPTION);
-    assertThat(restClient.evict(SOURCE_NAME, Constants.GROUPS_BYINCLUDE,
-        mock(AccountGroup.UUID.class))).isTrue();
-  }
-
-  @Test
-  public void testEvictGroupsMembersOK() throws Exception {
-    setupMocks(Constants.GROUPS_MEMBERS, EMPTY_JSON, OK_RESPONSE,
-        DO_NOT_THROW_EXCEPTION);
-    assertThat(restClient.evict(SOURCE_NAME, Constants.GROUPS_MEMBERS,
-        mock(AccountGroup.UUID.class))).isTrue();
-  }
-
-  @Test
-  public void testEvictProjectListOK() throws Exception {
-    setupMocks(Constants.PROJECT_LIST, EMPTY_JSON, OK_RESPONSE,
-        DO_NOT_THROW_EXCEPTION);
-    assertThat(
-        restClient.evict(SOURCE_NAME, Constants.PROJECT_LIST, new Object()))
-            .isTrue();
-  }
-
-  @Test
-  public void testEvictCacheFailed() throws Exception {
-    setupMocks(Constants.DEFAULT, EMPTY_JSON2, FAIL_RESPONSE,
-        DO_NOT_THROW_EXCEPTION);
-    assertThat(restClient.evict(SOURCE_NAME, Constants.DEFAULT, EMPTY_JSON))
-        .isFalse();
-  }
-
-  @Test
-  public void testEvictCacheThrowsException() throws Exception {
-    setupMocks(Constants.DEFAULT, EMPTY_JSON2, FAIL_RESPONSE, THROW_EXCEPTION);
-    assertThat(restClient.evict(SOURCE_NAME, Constants.DEFAULT, EMPTY_JSON))
-        .isFalse();
-  }
-
-  private void setupMocks(String cacheName, String json, boolean ok,
-      boolean exception) throws IOException {
-    String request = Joiner.on("/").join("/plugins", PLUGIN_NAME, SOURCE_NAME,
-        EVICT, cacheName);
-    HttpSession httpSession = mock(HttpSession.class);
-    if (exception) {
-      doThrow(new IOException()).when(httpSession).post(request, json);
-    } else {
-      CacheResult result = new CacheResult(ok, "Error");
-      when(httpSession.post(request, json)).thenReturn(result);
-    }
-
-    restClient = new RestSession(httpSession, PLUGIN_NAME);
-  }
-}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/evictcache/ConfigurationTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
similarity index 69%
rename from src/test/java/com/ericsson/gerrit/plugins/evictcache/ConfigurationTest.java
rename to src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
index 88ddc54..f9225c3 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/evictcache/ConfigurationTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
@@ -12,9 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.when;
 
 import com.google.gerrit.server.config.PluginConfig;
@@ -44,7 +46,7 @@
   @Mock
   private PluginConfig configMock;
   private Configuration configuration;
-  private String pluginName = "evict-cache";
+  private String pluginName = "high-availability";
 
   @Before
   public void setUp() throws Exception {
@@ -62,7 +64,12 @@
     assertThat(configuration.getSocketTimeout()).isEqualTo(TIMEOUT);
     assertThat(configuration.getMaxTries()).isEqualTo(MAX_TRIES);
     assertThat(configuration.getRetryInterval()).isEqualTo(RETRY_INTERVAL);
-    assertThat(configuration.getThreadPoolSize()).isEqualTo(THREAD_POOL_SIZE);
+    assertThat(configuration.getIndexThreadPoolSize())
+        .isEqualTo(THREAD_POOL_SIZE);
+    assertThat(configuration.getEventThreadPoolSize())
+        .isEqualTo(THREAD_POOL_SIZE);
+    assertThat(configuration.getCacheThreadPoolSize())
+        .isEqualTo(THREAD_POOL_SIZE);
   }
 
   @Test
@@ -75,7 +82,9 @@
     assertThat(configuration.getSocketTimeout()).isEqualTo(0);
     assertThat(configuration.getMaxTries()).isEqualTo(0);
     assertThat(configuration.getRetryInterval()).isEqualTo(0);
-    assertThat(configuration.getThreadPoolSize()).isEqualTo(0);
+    assertThat(configuration.getIndexThreadPoolSize()).isEqualTo(0);
+    assertThat(configuration.getEventThreadPoolSize()).isEqualTo(0);
+    assertThat(configuration.getCacheThreadPoolSize()).isEqualTo(0);
   }
 
   @Test
@@ -87,6 +96,21 @@
     assertThat(configuration.getUrl()).isEqualTo(URL);
   }
 
+  @Test
+  public void testIllegalArgumentExceptionReturnDefaultValue() throws Exception {
+    when(configMock.getInt(anyString(), anyInt()))
+        .thenThrow(new IllegalArgumentException("some message"));
+
+    configuration = new Configuration(cfgFactoryMock, pluginName);
+    assertThat(configuration.getConnectionTimeout()).isEqualTo(5000);
+    assertThat(configuration.getSocketTimeout()).isEqualTo(5000);
+    assertThat(configuration.getMaxTries()).isEqualTo(5);
+    assertThat(configuration.getRetryInterval()).isEqualTo(1000);
+    assertThat(configuration.getIndexThreadPoolSize()).isEqualTo(1);
+    assertThat(configuration.getEventThreadPoolSize()).isEqualTo(1);
+    assertThat(configuration.getCacheThreadPoolSize()).isEqualTo(1);
+  }
+
   private void buildMocks(boolean values) {
     when(configMock.getString("url")).thenReturn(values ? URL : null);
     when(configMock.getString("user")).thenReturn(values ? USER : null);
@@ -99,7 +123,11 @@
         .thenReturn(values ? MAX_TRIES : 0);
     when(configMock.getInt("retryInterval", RETRY_INTERVAL))
         .thenReturn(values ? RETRY_INTERVAL : 0);
-    when(configMock.getInt("threadPoolSize", THREAD_POOL_SIZE))
+    when(configMock.getInt("indexThreadPoolSize", THREAD_POOL_SIZE))
+        .thenReturn(values ? THREAD_POOL_SIZE : 0);
+    when(configMock.getInt("eventThreadPoolSize", THREAD_POOL_SIZE))
+        .thenReturn(values ? THREAD_POOL_SIZE : 0);
+    when(configMock.getInt("cacheThreadPoolSize", THREAD_POOL_SIZE))
         .thenReturn(values ? THREAD_POOL_SIZE : 0);
 
     configuration = new Configuration(cfgFactoryMock, pluginName);
diff --git a/src/test/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionIT.java
similarity index 80%
rename from src/test/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheIT.java
rename to src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionIT.java
index b44ac31..0c8ba85 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheIT.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionIT.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.cache;
 
 import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
 import static com.github.tomakehurst.wiremock.client.WireMock.givenThat;
@@ -26,6 +26,7 @@
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.PluginDaemonTest;
 
+import com.ericsson.gerrit.plugins.highavailability.cache.Constants;
 import com.github.tomakehurst.wiremock.http.Request;
 import com.github.tomakehurst.wiremock.http.RequestListener;
 import com.github.tomakehurst.wiremock.http.Response;
@@ -38,19 +39,18 @@
 import java.util.concurrent.TimeUnit;
 
 @NoHttpd
-public class EvictCacheIT extends PluginDaemonTest {
+public class CacheEvictionIT extends PluginDaemonTest {
 
   @Rule
-  public WireMockRule wireMockRule = new WireMockRule(Constants.PORT);
+  public WireMockRule wireMockRule = new WireMockRule(18888);
 
   @Test
   @GerritConfigs({
-      @GerritConfig(name = "plugin.evict-cache.url", value = Constants.URL),
-      @GerritConfig(name = "plugin.evict-cache.user", value = "admin")
-  })
+      @GerritConfig(name = "plugin.high-availability.url", value = "http://localhost:18888"),
+      @GerritConfig(name = "plugin.high-availability.user", value = "admin")})
   public void flushAndSendPost() throws Exception {
     final String flushRequest =
-        Constants.ENDPOINT_BASE + Constants.PROJECT_LIST;
+        "/plugins/high-availability/cache/" + Constants.PROJECT_LIST;
     wireMockRule.addMockServiceRequestListener(new RequestListener() {
       @Override
       public void requestReceived(Request request, Response response) {
@@ -67,7 +67,7 @@
     adminSshSession
         .exec("gerrit flush-caches --cache " + Constants.PROJECT_LIST);
     synchronized (flushRequest) {
-      flushRequest.wait(TimeUnit.SECONDS.toMillis(2));
+      flushRequest.wait(TimeUnit.SECONDS.toMillis(5));
     }
     verify(postRequestedFor(urlEqualTo(flushRequest)));
   }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheExecutorProviderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheExecutorProviderTest.java
similarity index 64%
copy from src/test/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheExecutorProviderTest.java
copy to src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheExecutorProviderTest.java
index a01b187..21e6cab 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheExecutorProviderTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheExecutorProviderTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.cache;
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Mockito.mock;
@@ -21,6 +21,8 @@
 
 import com.google.gerrit.server.git.WorkQueue;
 
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -28,37 +30,36 @@
 import org.mockito.junit.MockitoJUnitRunner;
 
 @RunWith(MockitoJUnitRunner.class)
-public class EvictCacheExecutorProviderTest {
-  private static final String PLUGIN_NAME = "evict-cache";
+public class CacheExecutorProviderTest {
 
   @Mock
   private WorkQueue.Executor executorMock;
-  private EvictCacheExecutorProvider evictCacheExecutorProvider;
+  private CacheExecutorProvider cacheExecutorProvider;
 
   @Before
   public void setUp() throws Exception {
     WorkQueue workQueueMock = mock(WorkQueue.class);
-    when(workQueueMock.createQueue(4,
-        "Evict cache [" + PLUGIN_NAME + " plugin]")).thenReturn(executorMock);
+    when(workQueueMock.createQueue(4, "Forward-cache-eviction-event"))
+        .thenReturn(executorMock);
     Configuration configMock = mock(Configuration.class);
-    when(configMock.getThreadPoolSize()).thenReturn(4);
+    when(configMock.getCacheThreadPoolSize()).thenReturn(4);
 
-    evictCacheExecutorProvider =
-        new EvictCacheExecutorProvider(workQueueMock, PLUGIN_NAME, configMock);
+    cacheExecutorProvider =
+        new CacheExecutorProvider(workQueueMock, configMock);
   }
 
   @Test
   public void shouldReturnExecutor() throws Exception {
-    assertThat(evictCacheExecutorProvider.get()).isEqualTo(executorMock);
+    assertThat(cacheExecutorProvider.get()).isEqualTo(executorMock);
   }
 
   @Test
   public void testStop() throws Exception {
-    evictCacheExecutorProvider.start();
-    assertThat(evictCacheExecutorProvider.get()).isEqualTo(executorMock);
-    evictCacheExecutorProvider.stop();
+    cacheExecutorProvider.start();
+    assertThat(cacheExecutorProvider.get()).isEqualTo(executorMock);
+    cacheExecutorProvider.stop();
     verify(executorMock).shutdown();
     verify(executorMock).unregisterWorkQueue();
-    assertThat(evictCacheExecutorProvider.get()).isNull();
+    assertThat(cacheExecutorProvider.get()).isNull();
   }
 }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheExecutorProviderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventExecutorProviderTest.java
similarity index 62%
copy from src/test/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheExecutorProviderTest.java
copy to src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventExecutorProviderTest.java
index a01b187..085ca0a 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheExecutorProviderTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventExecutorProviderTest.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Ericsson
+// Copyright (C) 2016 Ericsson
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.event;
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Mockito.mock;
@@ -21,6 +21,9 @@
 
 import com.google.gerrit.server.git.WorkQueue;
 
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
+import com.ericsson.gerrit.plugins.highavailability.event.EventExecutorProvider;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -28,37 +31,35 @@
 import org.mockito.junit.MockitoJUnitRunner;
 
 @RunWith(MockitoJUnitRunner.class)
-public class EvictCacheExecutorProviderTest {
-  private static final String PLUGIN_NAME = "evict-cache";
-
+public class EventExecutorProviderTest {
   @Mock
   private WorkQueue.Executor executorMock;
-  private EvictCacheExecutorProvider evictCacheExecutorProvider;
+  private EventExecutorProvider eventsExecutorProvider;
 
   @Before
   public void setUp() throws Exception {
     WorkQueue workQueueMock = mock(WorkQueue.class);
-    when(workQueueMock.createQueue(4,
-        "Evict cache [" + PLUGIN_NAME + " plugin]")).thenReturn(executorMock);
+    when(workQueueMock.createQueue(4, "Forward-stream-event"))
+        .thenReturn(executorMock);
     Configuration configMock = mock(Configuration.class);
-    when(configMock.getThreadPoolSize()).thenReturn(4);
+    when(configMock.getEventThreadPoolSize()).thenReturn(4);
 
-    evictCacheExecutorProvider =
-        new EvictCacheExecutorProvider(workQueueMock, PLUGIN_NAME, configMock);
+    eventsExecutorProvider =
+        new EventExecutorProvider(workQueueMock, configMock);
   }
 
   @Test
   public void shouldReturnExecutor() throws Exception {
-    assertThat(evictCacheExecutorProvider.get()).isEqualTo(executorMock);
+    assertThat(eventsExecutorProvider.get()).isEqualTo(executorMock);
   }
 
   @Test
   public void testStop() throws Exception {
-    evictCacheExecutorProvider.start();
-    assertThat(evictCacheExecutorProvider.get()).isEqualTo(executorMock);
-    evictCacheExecutorProvider.stop();
+    eventsExecutorProvider.start();
+    assertThat(eventsExecutorProvider.get()).isEqualTo(executorMock);
+    eventsExecutorProvider.stop();
     verify(executorMock).shutdown();
     verify(executorMock).unregisterWorkQueue();
-    assertThat(evictCacheExecutorProvider.get()).isNull();
+    assertThat(eventsExecutorProvider.get()).isNull();
   }
 }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandlerTest.java
new file mode 100644
index 0000000..07ab3c5
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandlerTest.java
@@ -0,0 +1,98 @@
+// Copyright (C) 2015 Ericsson
+//
+// 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 com.ericsson.gerrit.plugins.highavailability.event;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.RefEvent;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Context;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+
+@RunWith(MockitoJUnitRunner.class)
+public class EventHandlerTest {
+  private static final String PLUGIN_NAME = "high-availability";
+
+  private Event event;
+  private EventHandler eventHandler;
+  @Mock
+  private Forwarder forwarder;
+
+  @Test
+  public void testRightEventAndNotForwarded() throws Exception {
+    setUpMocks(true);
+    eventHandler.onEvent(event);
+    verify(forwarder).send(event);
+  }
+
+  @Test
+  public void testRightEventIsForwarded() throws Exception {
+    setUpMocks(true);
+    Context.setForwardedEvent(true);
+    eventHandler.onEvent(event);
+    Context.unsetForwardedEvent();
+    verifyZeroInteractions(forwarder);
+  }
+
+  @Test
+  public void testBadEventAndNotForwarded() throws Exception {
+    setUpMocks(false);
+    eventHandler.onEvent(event);
+    verifyZeroInteractions(forwarder);
+  }
+
+  @Test
+  public void testBadEventAndItIsForwarded() throws Exception {
+    setUpMocks(false);
+    Context.setForwardedEvent(true);
+    eventHandler.onEvent(event);
+    Context.unsetForwardedEvent();
+    verifyZeroInteractions(forwarder);
+  }
+
+  private void setUpMocks(boolean rightEvent) {
+    ScheduledThreadPoolExecutor pool = new PoolMock(1);
+    if (rightEvent) {
+      event = mock(RefEvent.class);
+    } else {
+      event = mock(Event.class);
+    }
+    eventHandler = new EventHandler(forwarder, pool, PLUGIN_NAME);
+  }
+
+  private class PoolMock extends ScheduledThreadPoolExecutor {
+    PoolMock(int corePoolSize) {
+      super(corePoolSize);
+    }
+
+    @Override
+    public void execute(Runnable command) {
+      assertThat(command.toString()).isEqualTo(String.format(
+          "[%s] Send event '%s' to target instance", PLUGIN_NAME, null));
+      command.run();
+    }
+  }
+}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/ForwardedAwareEventBrokerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/ForwardedAwareEventBrokerTest.java
new file mode 100644
index 0000000..713f7ef
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/ForwardedAwareEventBrokerTest.java
@@ -0,0 +1,61 @@
+// Copyright (C) 2016 Ericsson
+//
+// 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 com.ericsson.gerrit.plugins.highavailability.event;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import com.google.gerrit.common.EventListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.server.events.Event;
+
+import com.ericsson.gerrit.plugins.highavailability.event.ForwardedAwareEventBroker;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Context;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class ForwardedAwareEventBrokerTest {
+
+  private EventListener listenerMock;
+  private ForwardedAwareEventBroker broker;
+  private Event event = new Event(null) {};
+
+  @Before
+  public void setUp() {
+    listenerMock = mock(EventListener.class);
+    DynamicSet<EventListener> listeners = DynamicSet.emptySet();
+    listeners.add(listenerMock);
+    broker = new ForwardedAwareEventBroker(null, listeners, null, null, null);
+  }
+
+  @Test
+  public void shouldDispatchEvent() {
+    broker.fireEventForUnrestrictedListeners(event);
+    verify(listenerMock).onEvent(event);
+  }
+
+  @Test
+  public void shouldNotDispatchForwardedEvents() {
+    Context.setForwardedEvent(true);
+    try {
+      broker.fireEventForUnrestrictedListeners(event);
+    } finally {
+      Context.unsetForwardedEvent();
+    }
+    verifyZeroInteractions(listenerMock);
+  }
+}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServletTest.java
similarity index 90%
rename from src/test/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheRestApiServletTest.java
rename to src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServletTest.java
index 3db1f75..4813a7d 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheRestApiServletTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServletTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
 import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
@@ -24,6 +24,8 @@
 import com.google.common.cache.Cache;
 import com.google.gerrit.extensions.registration.DynamicMap;
 
+import com.ericsson.gerrit.plugins.highavailability.cache.Constants;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -37,7 +39,7 @@
 import javax.servlet.http.HttpServletResponse;
 
 @RunWith(MockitoJUnitRunner.class)
-public class EvictCacheRestApiServletTest {
+public class CacheRestApiServletTest {
   @Mock
   private HttpServletRequest request;
   @Mock
@@ -46,11 +48,11 @@
   private BufferedReader reader;
   @Mock
   private DynamicMap<Cache<?, ?>> cacheMap;
-  private EvictCacheRestApiServlet servlet;
+  private CacheRestApiServlet servlet;
 
   @Before
   public void setUp() {
-    servlet = new EvictCacheRestApiServlet(cacheMap);
+    servlet = new CacheRestApiServlet(cacheMap);
   }
 
   @Test
@@ -86,7 +88,7 @@
 
   @Test
   public void evictDefault() throws Exception {
-    configureMocksFor(Constants.DEFAULT);
+    configureMocksFor(Constants.PROJECTS);
     verifyResponseIsOK();
   }
 
@@ -119,7 +121,7 @@
     when(request.getPathInfo()).thenReturn("/" + cacheName);
     when(request.getReader()).thenReturn(reader);
 
-    if (Constants.DEFAULT.equals(cacheName)) {
+    if (Constants.PROJECTS.equals(cacheName)) {
       when(reader.readLine()).thenReturn("abc");
     } else if (Constants.GROUPS_BYINCLUDE.equals(cacheName)
         || Constants.GROUPS_MEMBERS.equals(cacheName)) {
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServletTest.java
new file mode 100644
index 0000000..27d46b7
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServletTest.java
@@ -0,0 +1,149 @@
+// Copyright (C) 2016 Ericsson
+//
+// 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 com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
+
+import static com.google.common.net.MediaType.JSON_UTF_8;
+import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
+import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
+import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
+import static javax.servlet.http.HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.common.net.MediaType;
+import com.google.gerrit.common.EventDispatcher;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.events.EventTypes;
+import com.google.gerrit.server.events.RefEvent;
+import com.google.gwtorm.client.KeyUtil;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.StandardKeyEncoder;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.EventRestApiServlet;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@RunWith(MockitoJUnitRunner.class)
+public class EventRestApiServletTest {
+  private static final String ERR_MSG = "some Error";
+
+  @Mock
+  private EventDispatcher dispatcher;
+  @Mock
+  private HttpServletRequest req;
+  @Mock
+  private HttpServletResponse rsp;
+  private EventRestApiServlet eventRestApiServlet;
+
+  @BeforeClass
+  public static void setup() {
+    EventTypes.register(RefReplicationDoneEvent.TYPE,
+        RefReplicationDoneEvent.class);
+    KeyUtil.setEncoderImpl(new StandardKeyEncoder());
+  }
+
+  @Before
+  public void createEventsRestApiServlet() throws Exception {
+    eventRestApiServlet = new EventRestApiServlet(dispatcher);
+    when(req.getContentType()).thenReturn(MediaType.JSON_UTF_8.toString());
+  }
+
+  @Test
+  public void testDoPostRefReplicationDoneEvent() throws Exception {
+    String event = "{\"project\":\"gerrit/some-project\",\"ref\":"
+        + "\"refs/changes/76/669676/2\",\"nodesCount\":1,\"type\":"
+        + "\"ref-replication-done\",\"eventCreatedOn\":1451415011}";
+    when(req.getReader())
+        .thenReturn(new BufferedReader(new StringReader(event)));
+    dispatcher.postEvent(any(RefReplicationDoneEvent.class));
+    eventRestApiServlet.doPost(req, rsp);
+    verify(rsp).setStatus(SC_NO_CONTENT);
+  }
+
+  @Test
+  public void testDoPostDispatcherFailure() throws Exception {
+    String event = "{\"project\":\"gerrit/some-project\",\"ref\":"
+        + "\"refs/changes/76/669676/2\",\"nodesCount\":1,\"type\":"
+        + "\"ref-replication-done\",\"eventCreatedOn\":1451415011}";
+    when(req.getReader())
+        .thenReturn(new BufferedReader(new StringReader(event)));
+    doThrow(new OrmException(ERR_MSG)).when(dispatcher)
+        .postEvent(any(RefReplicationDoneEvent.class));
+    eventRestApiServlet.doPost(req, rsp);
+    verify(rsp).sendError(SC_NOT_FOUND, "Change not found\n");
+  }
+
+  @Test
+  public void testDoPostBadRequest() throws Exception {
+    doThrow(new IOException(ERR_MSG)).when(req).getReader();
+    eventRestApiServlet.doPost(req, rsp);
+    verify(rsp).sendError(SC_BAD_REQUEST, ERR_MSG);
+  }
+
+  @Test
+  public void testDoPostWrongMediaType() throws Exception {
+    when(req.getContentType())
+        .thenReturn(MediaType.APPLICATION_XML_UTF_8.toString());
+    eventRestApiServlet.doPost(req, rsp);
+    verify(rsp).sendError(SC_UNSUPPORTED_MEDIA_TYPE,
+        "Expecting " + JSON_UTF_8.toString() + " content type");
+  }
+
+  @Test
+  public void testDoPostErrorWhileSendingErrorMessage() throws Exception {
+    doThrow(new IOException(ERR_MSG)).when(req).getReader();
+    doThrow(new IOException("someOtherError")).when(rsp)
+        .sendError(SC_BAD_REQUEST, ERR_MSG);
+    eventRestApiServlet.doPost(req, rsp);
+  }
+
+  static class RefReplicationDoneEvent extends RefEvent {
+    public static final String TYPE = "ref-replication-done";
+    public final String project;
+    public final String ref;
+    public final int nodesCount;
+
+    public RefReplicationDoneEvent(String project, String ref, int nodesCount) {
+      super(TYPE);
+      this.project = project;
+      this.ref = ref;
+      this.nodesCount = nodesCount;
+    }
+
+    @Override
+    public Project.NameKey getProjectNameKey() {
+      return new Project.NameKey(project);
+    }
+
+    @Override
+    public String getRefName() {
+      return ref;
+    }
+  }
+}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/evictcache/GsonParserTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonParserTest.java
similarity index 86%
rename from src/test/java/com/ericsson/gerrit/plugins/evictcache/GsonParserTest.java
rename to src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonParserTest.java
index 638456d..b72c21a 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/evictcache/GsonParserTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonParserTest.java
@@ -12,13 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 
+import com.ericsson.gerrit.plugins.highavailability.cache.Constants;
+
 import org.junit.Test;
 
 public class GsonParserTest {
@@ -52,8 +54,9 @@
   @Test
   public void StringParse() {
     String key = "key";
-    String json = GsonParser.toJson(Constants.DEFAULT, key);
-    assertThat(key).isEqualTo(GsonParser.fromJson(Constants.DEFAULT, json));
+    String json = GsonParser.toJson(Constants.PROJECTS, key);
+    assertThat(key)
+        .isEqualTo(GsonParser.fromJson(Constants.PROJECTS, json));
   }
 
   @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/evictcache/HttpClientProviderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpClientProviderTest.java
similarity index 80%
rename from src/test/java/com/ericsson/gerrit/plugins/evictcache/HttpClientProviderTest.java
rename to src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpClientProviderTest.java
index e64cf7c..8c01516 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/evictcache/HttpClientProviderTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpClientProviderTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Mockito.when;
@@ -22,6 +22,8 @@
 import com.google.inject.Injector;
 import com.google.inject.Scopes;
 
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
+
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.junit.Before;
 import org.junit.Test;
@@ -49,12 +51,14 @@
   @Test
   public void testGet() throws Exception {
     Injector injector = Guice.createInjector(new TestModule());
-    CloseableHttpClient httpClient1 =
-        injector.getInstance(CloseableHttpClient.class);
-    assertThat(httpClient1).isNotNull();
-    CloseableHttpClient httpClient2 =
-        injector.getInstance(CloseableHttpClient.class);
-    assertThat(httpClient1).isEqualTo(httpClient2);
+    try (CloseableHttpClient httpClient1 =
+        injector.getInstance(CloseableHttpClient.class)) {
+      assertThat(httpClient1).isNotNull();
+      try (CloseableHttpClient httpClient2 =
+          injector.getInstance(CloseableHttpClient.class)) {
+        assertThat(httpClient1).isEqualTo(httpClient2);
+      }
+    }
   }
 
   class TestModule extends LifecycleModule {
diff --git a/src/test/java/com/ericsson/gerrit/plugins/evictcache/CacheResponseHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpResponseHandlerTest.java
similarity index 83%
rename from src/test/java/com/ericsson/gerrit/plugins/evictcache/CacheResponseHandlerTest.java
rename to src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpResponseHandlerTest.java
index 8e94727..b612770 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/evictcache/CacheResponseHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpResponseHandlerTest.java
@@ -12,13 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-import com.ericsson.gerrit.plugins.evictcache.CacheResponseHandler.CacheResult;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.HttpResponseHandler.HttpResult;
 
 import org.apache.http.HttpResponse;
 import org.apache.http.StatusLine;
@@ -28,23 +28,23 @@
 
 import java.io.UnsupportedEncodingException;
 
-public class CacheResponseHandlerTest {
+public class HttpResponseHandlerTest {
   private static final int ERROR = 400;
   private static final int OK = 204;
   private static final String EMPTY_ENTITY = "";
   private static final String ERROR_ENTITY = "Error";
 
-  private CacheResponseHandler handler;
+  private HttpResponseHandler handler;
 
   @Before
   public void setUp() throws Exception {
-    handler = new CacheResponseHandler();
+    handler = new HttpResponseHandler();
   }
 
   @Test
   public void testIsSuccessful() throws Exception {
     HttpResponse response = setupMocks(OK, EMPTY_ENTITY);
-    CacheResult result = handler.handleResponse(response);
+    HttpResult result = handler.handleResponse(response);
     assertThat(result.isSuccessful()).isTrue();
     assertThat(result.getMessage()).isEmpty();
   }
@@ -52,7 +52,7 @@
   @Test
   public void testIsNotSuccessful() throws Exception {
     HttpResponse response = setupMocks(ERROR, ERROR_ENTITY);
-    CacheResult result = handler.handleResponse(response);
+    HttpResult result = handler.handleResponse(response);
     assertThat(result.isSuccessful()).isFalse();
     assertThat(result.getMessage()).contains(ERROR_ENTITY);
   }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/evictcache/HttpSessionTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSessionTest.java
similarity index 64%
rename from src/test/java/com/ericsson/gerrit/plugins/evictcache/HttpSessionTest.java
rename to src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSessionTest.java
index f45e52d..7b556b7 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/evictcache/HttpSessionTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSessionTest.java
@@ -12,23 +12,25 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.delete;
+import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
 import static com.github.tomakehurst.wiremock.client.WireMock.post;
-import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
 import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-import com.ericsson.gerrit.plugins.evictcache.CacheResponseHandler.CacheResult;
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.HttpResponseHandler.HttpResult;
 import com.github.tomakehurst.wiremock.http.Fault;
 import com.github.tomakehurst.wiremock.junit.WireMockRule;
 import com.github.tomakehurst.wiremock.stubbing.Scenario;
 
 import org.junit.Before;
-import org.junit.Rule;
+import org.junit.ClassRule;
 import org.junit.Test;
 
 import java.net.SocketTimeoutException;
@@ -42,8 +44,8 @@
   private static final int NOT_FOUND = 404;
   private static final int UNAUTHORIZED = 401;
 
-  private static final String ENDPOINT = Constants.ENDPOINT_BASE + "cache";
-  private static final String EMPTYJSON = "{}";
+  private static final String ENDPOINT = "/plugins/high-availability/index/1";
+  private static final String BODY = "SerializedEvent";
   private static final String ERROR_MESSAGE = "Error message";
   private static final String REQUEST_MADE = "Request made";
   private static final String SECOND_TRY = "Second try";
@@ -53,13 +55,14 @@
 
   private HttpSession httpSession;
 
-  @Rule
-  public WireMockRule wireMockRule = new WireMockRule(Constants.PORT);
+  @ClassRule
+  public static WireMockRule wireMockRule = new WireMockRule(0);
 
   @Before
   public void setUp() throws Exception {
+    String url = "http://localhost:" + wireMockRule.port();
     Configuration cfg = mock(Configuration.class);
-    when(cfg.getUrl()).thenReturn(Constants.URL);
+    when(cfg.getUrl()).thenReturn(url);
     when(cfg.getUser()).thenReturn("user");
     when(cfg.getPassword()).thenReturn("pass");
     when(cfg.getMaxTries()).thenReturn(MAX_TRIES);
@@ -68,22 +71,40 @@
     when(cfg.getRetryInterval()).thenReturn(RETRY_INTERVAL);
 
     httpSession =
-        new HttpSession(new HttpClientProvider(cfg).get(), Constants.URL);
+        new HttpSession(new HttpClientProvider(cfg).get(), url);
+    wireMockRule.resetRequests();
   }
 
   @Test
-  public void testResponseOK() throws Exception {
-    stubFor(post(urlEqualTo(ENDPOINT)).willReturn(aResponse().withStatus(OK)));
-    assertThat(httpSession.post(ENDPOINT, EMPTYJSON).isSuccessful()).isTrue();
+  public void testPostResponseOK() throws Exception {
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT))
+        .willReturn(aResponse().withStatus(OK)));
+
+    assertThat(httpSession.post(ENDPOINT).isSuccessful()).isTrue();
+  }
+
+  @Test
+  public void testPostResponseWithContentOK() throws Exception {
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT))
+        .withRequestBody(equalTo(BODY)).willReturn(aResponse().withStatus(OK)));
+    assertThat(httpSession.post(ENDPOINT, BODY).isSuccessful()).isTrue();
+  }
+
+  @Test
+  public void testDeleteResponseOK() throws Exception {
+    wireMockRule.givenThat(delete(urlEqualTo(ENDPOINT))
+        .willReturn(aResponse().withStatus(OK)));
+
+    assertThat(httpSession.delete(ENDPOINT).isSuccessful()).isTrue();
   }
 
   @Test
   public void testNotAuthorized() throws Exception {
     String expected = "unauthorized";
-    stubFor(post(urlEqualTo(ENDPOINT)).willReturn(
-        aResponse().withStatus(UNAUTHORIZED).withBody(expected)));
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT))
+        .willReturn(aResponse().withStatus(UNAUTHORIZED).withBody(expected)));
 
-    CacheResult result = httpSession.post(ENDPOINT, EMPTYJSON);
+    HttpResult result = httpSession.post(ENDPOINT);
     assertThat(result.isSuccessful()).isFalse();
     assertThat(result.getMessage()).isEqualTo(expected);
   }
@@ -91,46 +112,46 @@
   @Test
   public void testNotFound() throws Exception {
     String expected = "not found";
-    stubFor(post(urlEqualTo(ENDPOINT)).willReturn(
-        aResponse().withStatus(NOT_FOUND).withBody(expected)));
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT))
+        .willReturn(aResponse().withStatus(NOT_FOUND).withBody(expected)));
 
-    CacheResult result = httpSession.post(ENDPOINT, EMPTYJSON);
+    HttpResult result = httpSession.post(ENDPOINT);
     assertThat(result.isSuccessful()).isFalse();
     assertThat(result.getMessage()).isEqualTo(expected);
   }
 
   @Test
   public void testBadResponseRetryThenOK() throws Exception {
-    stubFor(post(urlEqualTo(ENDPOINT)).inScenario(RETRY_AT_ERROR)
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT)).inScenario(RETRY_AT_ERROR)
         .whenScenarioStateIs(Scenario.STARTED).willSetStateTo(REQUEST_MADE)
         .willReturn(aResponse().withStatus(ERROR)));
-    stubFor(post(urlEqualTo(ENDPOINT)).inScenario(RETRY_AT_ERROR)
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT)).inScenario(RETRY_AT_ERROR)
         .whenScenarioStateIs(REQUEST_MADE)
         .willReturn(aResponse().withStatus(OK)));
 
-    assertThat(httpSession.post(ENDPOINT, EMPTYJSON).isSuccessful()).isTrue();
+    assertThat(httpSession.post(ENDPOINT).isSuccessful()).isTrue();
   }
 
   @Test
   public void testBadResponseRetryThenGiveUp() throws Exception {
-    stubFor(post(urlEqualTo(ENDPOINT)).willReturn(
-        aResponse().withStatus(ERROR).withBody(ERROR_MESSAGE)));
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT))
+        .willReturn(aResponse().withStatus(ERROR).withBody(ERROR_MESSAGE)));
 
-    assertThat(httpSession.post(ENDPOINT, EMPTYJSON).isSuccessful()).isFalse();
-    assertThat(httpSession.post(ENDPOINT, EMPTYJSON).getMessage())
-        .isEqualTo(ERROR_MESSAGE);
+    HttpResult result = httpSession.post(ENDPOINT);
+    assertThat(result.isSuccessful()).isFalse();
+    assertThat(result.getMessage()).isEqualTo(ERROR_MESSAGE);
   }
 
   @Test
   public void testRetryAfterDelay() throws Exception {
-    stubFor(post(urlEqualTo(ENDPOINT)).inScenario(RETRY_AT_DELAY)
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT)).inScenario(RETRY_AT_DELAY)
         .whenScenarioStateIs(Scenario.STARTED).willSetStateTo(REQUEST_MADE)
         .willReturn(aResponse().withStatus(ERROR).withFixedDelay(TIMEOUT / 2)));
-    stubFor(post(urlEqualTo(ENDPOINT)).inScenario(RETRY_AT_DELAY)
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT)).inScenario(RETRY_AT_DELAY)
         .whenScenarioStateIs(REQUEST_MADE)
         .willReturn(aResponse().withStatus(OK)));
 
-    assertThat(httpSession.post(ENDPOINT, EMPTYJSON).isSuccessful()).isTrue();
+    assertThat(httpSession.post(ENDPOINT).isSuccessful()).isTrue();
   }
 
   @Test
@@ -148,7 +169,7 @@
         .whenScenarioStateIs(THIRD_TRY)
         .willReturn(aResponse().withStatus(OK)));
 
-    assertThat(httpSession.post(ENDPOINT, EMPTYJSON).isSuccessful()).isTrue();
+    assertThat(httpSession.post(ENDPOINT).isSuccessful()).isTrue();
   }
 
   @Test(expected = SocketTimeoutException.class)
@@ -166,23 +187,23 @@
         .whenScenarioStateIs(THIRD_TRY)
         .willReturn(aResponse().withFixedDelay(TIMEOUT)));
 
-    httpSession.post(ENDPOINT, EMPTYJSON);
+    httpSession.post(ENDPOINT);
   }
 
   @Test
   public void testGiveUpAtTimeout() throws Exception {
-    stubFor(post(urlEqualTo(ENDPOINT)).inScenario(RETRY_AT_DELAY)
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT)).inScenario(RETRY_AT_DELAY)
         .whenScenarioStateIs(Scenario.STARTED).willSetStateTo(REQUEST_MADE)
         .willReturn(aResponse().withStatus(ERROR).withFixedDelay(TIMEOUT)));
 
-    assertThat(httpSession.post(ENDPOINT, EMPTYJSON).isSuccessful()).isFalse();
+    assertThat(httpSession.post(ENDPOINT).isSuccessful()).isFalse();
   }
 
   @Test
   public void testResponseWithMalformedResponse() throws Exception {
-    stubFor(post(urlEqualTo(ENDPOINT)).willReturn(
-        aResponse().withFault(Fault.MALFORMED_RESPONSE_CHUNK)));
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT))
+        .willReturn(aResponse().withFault(Fault.MALFORMED_RESPONSE_CHUNK)));
 
-    assertThat(httpSession.post(ENDPOINT, EMPTYJSON).isSuccessful()).isFalse();
+    assertThat(httpSession.post(ENDPOINT).isSuccessful()).isFalse();
   }
 }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexRestApiServletTest.java
new file mode 100644
index 0000000..3aa7009
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexRestApiServletTest.java
@@ -0,0 +1,169 @@
+// Copyright (C) 2016 Ericsson
+//
+// 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 com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
+
+import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
+import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
+import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.server.ChangeAccess;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.index.change.ChangeIndexer;
+import com.google.gwtorm.client.KeyUtil;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.gwtorm.server.StandardKeyEncoder;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@RunWith(MockitoJUnitRunner.class)
+public class IndexRestApiServletTest {
+  private static final boolean CHANGE_EXISTS = true;
+  private static final boolean CHANGE_DOES_NOT_EXIST = false;
+  private static final boolean DO_NOT_THROW_IO_EXCEPTION = false;
+  private static final boolean DO_NOT_THROW_ORM_EXCEPTION = false;
+  private static final boolean THROW_IO_EXCEPTION = true;
+  private static final boolean THROW_ORM_EXCEPTION = true;
+  private static final String CHANGE_NUMBER = "1";
+
+  @Mock
+  private ChangeIndexer indexer;
+  @Mock
+  private SchemaFactory<ReviewDb> schemaFactory;
+  @Mock
+  private ReviewDb db;
+  @Mock
+  private HttpServletRequest req;
+  @Mock
+  private HttpServletResponse rsp;
+  private Change.Id id;
+  private Change change;
+  private IndexRestApiServlet indexRestApiServlet;
+
+  @BeforeClass
+  public static void setup() {
+    KeyUtil.setEncoderImpl(new StandardKeyEncoder());
+  }
+
+  @Before
+  public void setUpMocks() {
+    indexRestApiServlet = new IndexRestApiServlet(indexer, schemaFactory);
+    id = Change.Id.parse(CHANGE_NUMBER);
+    when(req.getPathInfo()).thenReturn("/index/" + CHANGE_NUMBER);
+    change = new Change(null, id, null, null, TimeUtil.nowTs());
+  }
+
+  @Test
+  public void changeIsIndexed() throws Exception {
+    setupPostMocks(CHANGE_EXISTS);
+    indexRestApiServlet.doPost(req, rsp);
+    verify(indexer, times(1)).index(db, change);
+    verify(rsp).setStatus(SC_NO_CONTENT);
+  }
+
+  @Test
+  public void changeToIndexDoNotExist() throws Exception {
+    setupPostMocks(CHANGE_DOES_NOT_EXIST);
+    indexRestApiServlet.doPost(req, rsp);
+    verify(indexer, times(1)).delete(id);
+    verify(rsp).setStatus(SC_NO_CONTENT);
+  }
+
+  @Test
+  public void schemaThrowsExceptionWhenLookingUpForChange() throws Exception {
+    setupPostMocks(CHANGE_EXISTS, THROW_ORM_EXCEPTION);
+    indexRestApiServlet.doPost(req, rsp);
+    verify(rsp).sendError(SC_NOT_FOUND, "Error trying to find a change \n");
+  }
+
+  @Test
+  public void indexerThrowsIOExceptionTryingToIndexChange() throws Exception {
+    setupPostMocks(CHANGE_EXISTS, DO_NOT_THROW_ORM_EXCEPTION,
+        THROW_IO_EXCEPTION);
+    indexRestApiServlet.doPost(req, rsp);
+    verify(rsp).sendError(SC_CONFLICT, "io-error");
+  }
+
+  @Test
+  public void changeIsDeletedFromIndex() throws Exception {
+    indexRestApiServlet.doDelete(req, rsp);
+    verify(indexer, times(1)).delete(id);
+    verify(rsp).setStatus(SC_NO_CONTENT);
+  }
+
+  @Test
+  public void indexerThrowsExceptionTryingToDeleteChange() throws Exception {
+    doThrow(new IOException("io-error")).when(indexer).delete(id);
+    indexRestApiServlet.doDelete(req, rsp);
+    verify(rsp).sendError(SC_CONFLICT, "io-error");
+  }
+
+  @Test
+  public void sendErrorThrowsIOException() throws Exception {
+    doThrow(new IOException("someError")).when(rsp).sendError(SC_NOT_FOUND,
+        "Error trying to find a change \n");
+    setupPostMocks(CHANGE_EXISTS, THROW_ORM_EXCEPTION);
+    indexRestApiServlet.doPost(req, rsp);
+    verify(rsp).sendError(SC_NOT_FOUND, "Error trying to find a change \n");
+    verifyZeroInteractions(indexer);
+  }
+
+  private void setupPostMocks(boolean changeExist) throws Exception {
+    setupPostMocks(changeExist, DO_NOT_THROW_ORM_EXCEPTION,
+        DO_NOT_THROW_IO_EXCEPTION);
+  }
+
+  private void setupPostMocks(boolean changeExist, boolean ormException)
+      throws OrmException, IOException {
+    setupPostMocks(changeExist, ormException, DO_NOT_THROW_IO_EXCEPTION);
+  }
+
+  private void setupPostMocks(boolean changeExist, boolean ormException,
+      boolean ioException) throws OrmException, IOException {
+    if (ormException) {
+      doThrow(new OrmException("")).when(schemaFactory).open();
+    } else {
+      when(schemaFactory.open()).thenReturn(db);
+      ChangeAccess ca = mock(ChangeAccess.class);
+      when(db.changes()).thenReturn(ca);
+      if (changeExist) {
+        when(ca.get(id)).thenReturn(change);
+        if (ioException) {
+          doThrow(new IOException("io-error")).when(indexer).index(db, change);
+        }
+      } else {
+        when(ca.get(id)).thenReturn(null);
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/evictcache/ModuleTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderModuleTest.java
similarity index 72%
rename from src/test/java/com/ericsson/gerrit/plugins/evictcache/ModuleTest.java
rename to src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderModuleTest.java
index 479ed90..ceedd2f 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/evictcache/ModuleTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderModuleTest.java
@@ -12,23 +12,24 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
+
 import org.junit.Test;
 
-public class ModuleTest {
+public class RestForwarderModuleTest {
 
   @Test
-  public void testSyncUrlProvider() {
+  public void testForwardUrlProvider() {
     Configuration configMock = mock(Configuration.class);
     String expected = "someUrl";
     when(configMock.getUrl()).thenReturn(expected);
-
-    Module module = new Module();
-    assertThat(module.syncUrl(configMock)).isEqualTo(expected);
+    RestForwarderModule module = new RestForwarderModule();
+    assertThat(module.forwardUrl(configMock)).isEqualTo(expected);
   }
 }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java
new file mode 100644
index 0000000..72e2462
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java
@@ -0,0 +1,232 @@
+// Copyright (C) 2015 Ericsson
+//
+// 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 com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.base.Joiner;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.events.Event;
+import com.google.gson.GsonBuilder;
+
+import com.ericsson.gerrit.plugins.highavailability.cache.Constants;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.HttpResponseHandler.HttpResult;
+
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class RestForwarderTest {
+  private static final String PLUGIN_NAME = "high-availability";
+  private static final String EMPTY_MSG = "";
+  private static final String ERROR_MSG = "Error";
+  private static final String EXCEPTION_MSG = "Exception";
+  private static final boolean SUCCESSFUL = true;
+  private static final boolean FAILED = false;
+  private static final boolean DO_NOT_THROW_EXCEPTION = false;
+  private static final boolean THROW_EXCEPTION = true;
+
+  //Index
+  private static final int CHANGE_NUMBER = 1;
+  private static final String DELETE_OP = "delete";
+  private static final String INDEX_OP = "index";
+
+  //Evict cache
+  private static final String EMPTY_JSON = "{}";
+  private static final String EMPTY_JSON2 = "\"{}\"";
+  private static final String ID_JSON = "{\"id\":0}";
+
+  private RestForwarder restForwarder;
+
+  @Test
+  public void testIndexChangeOK() throws Exception {
+    setUpMocksForIndex(INDEX_OP, SUCCESSFUL, EMPTY_MSG, DO_NOT_THROW_EXCEPTION);
+    assertThat(restForwarder.indexChange(CHANGE_NUMBER)).isTrue();
+  }
+
+  @Test
+  public void testIndexChangeFailed() throws Exception {
+    setUpMocksForIndex(INDEX_OP, FAILED, ERROR_MSG, DO_NOT_THROW_EXCEPTION);
+    assertThat(restForwarder.indexChange(CHANGE_NUMBER)).isFalse();
+  }
+
+  @Test
+  public void testIndexChangeThrowsException() throws Exception {
+    setUpMocksForIndex(INDEX_OP, FAILED, EXCEPTION_MSG, THROW_EXCEPTION);
+    assertThat(restForwarder.indexChange(CHANGE_NUMBER)).isFalse();
+  }
+
+  @Test
+  public void testChangeDeletedFromIndexOK() throws Exception {
+    setUpMocksForIndex(DELETE_OP, SUCCESSFUL, EMPTY_MSG,
+        DO_NOT_THROW_EXCEPTION);
+    assertThat(restForwarder.deleteChangeFromIndex(CHANGE_NUMBER)).isTrue();
+  }
+
+  @Test
+  public void testChangeDeletedFromIndexFailed() throws Exception {
+    setUpMocksForIndex(DELETE_OP, FAILED, ERROR_MSG, DO_NOT_THROW_EXCEPTION);
+    assertThat(restForwarder.deleteChangeFromIndex(CHANGE_NUMBER)).isFalse();
+  }
+
+  @Test
+  public void testChangeDeletedFromThrowsException() throws Exception {
+    setUpMocksForIndex(DELETE_OP, FAILED, EXCEPTION_MSG, THROW_EXCEPTION);
+    assertThat(restForwarder.deleteChangeFromIndex(CHANGE_NUMBER)).isFalse();
+  }
+
+  private void setUpMocksForIndex(String operation,
+      boolean isOperationSuccessful, String msg, boolean exception)
+      throws Exception {
+    String request =
+        Joiner.on("/").join("/plugins", PLUGIN_NAME, INDEX_OP, CHANGE_NUMBER);
+    HttpSession httpSession = mock(HttpSession.class);
+    if (exception) {
+      if (operation.equals(INDEX_OP)) {
+        doThrow(new IOException()).when(httpSession).post(request);
+      } else {
+        doThrow(new IOException()).when(httpSession).delete(request);
+      }
+    } else {
+      HttpResult result = new HttpResult(isOperationSuccessful, msg);
+      if (operation.equals(INDEX_OP)) {
+        when(httpSession.post(request)).thenReturn(result);
+      } else {
+        when(httpSession.delete(request)).thenReturn(result);
+      }
+    }
+    restForwarder = new RestForwarder(httpSession, PLUGIN_NAME);
+  }
+
+  @Test
+  public void testEventSentOK() throws Exception {
+    Event event = setUpMocksForEvent(SUCCESSFUL, EMPTY_MSG, DO_NOT_THROW_EXCEPTION);
+    assertThat(restForwarder.send(event)).isTrue();
+  }
+
+  @Test
+  public void testEventSentFailed() throws Exception {
+    Event event = setUpMocksForEvent(FAILED, ERROR_MSG, DO_NOT_THROW_EXCEPTION);
+    assertThat(restForwarder.send(event)).isFalse();
+  }
+
+  @Test
+  public void testEventSentThrowsException() throws Exception {
+    Event event = setUpMocksForEvent(FAILED, EXCEPTION_MSG, THROW_EXCEPTION);
+    assertThat(restForwarder.send(event)).isFalse();
+  }
+
+  private Event setUpMocksForEvent(boolean isOperationSuccessful, String msg,
+      boolean exception) throws Exception {
+    Event event = new EventTest();
+    String content = new GsonBuilder().create().toJson(event);
+    HttpSession httpSession = mock(HttpSession.class);
+    String request = Joiner.on("/").join("/plugins", PLUGIN_NAME, "event");
+    if (exception) {
+      doThrow(new IOException()).when(httpSession).post(request, content);
+    } else {
+      HttpResult result = new HttpResult(isOperationSuccessful, msg);
+      when(httpSession.post(request, content)).thenReturn(result);
+    }
+    restForwarder = new RestForwarder(httpSession, PLUGIN_NAME);
+    return event;
+  }
+
+  private class EventTest extends Event {
+    public EventTest() {
+      super("test-event");
+    }
+  }
+
+  @Test
+  public void testEvictCacheOK() throws Exception {
+    setupMocksForCache(Constants.PROJECTS, EMPTY_JSON2, SUCCESSFUL,
+        DO_NOT_THROW_EXCEPTION);
+    assertThat(restForwarder.evict(Constants.PROJECTS, EMPTY_JSON)).isTrue();
+  }
+
+  @Test
+  public void testEvictAccountsOK() throws Exception {
+    setupMocksForCache(Constants.ACCOUNTS, ID_JSON, SUCCESSFUL,
+        DO_NOT_THROW_EXCEPTION);
+    assertThat(restForwarder.evict(Constants.ACCOUNTS, mock(Account.Id.class)))
+        .isTrue();
+  }
+
+  @Test
+  public void testEvictGroupsOK() throws Exception {
+    setupMocksForCache(Constants.GROUPS, ID_JSON, SUCCESSFUL,
+        DO_NOT_THROW_EXCEPTION);
+    assertThat(
+        restForwarder.evict(Constants.GROUPS, mock(AccountGroup.Id.class)))
+            .isTrue();
+  }
+
+  @Test
+  public void testEvictGroupsByIncludeOK() throws Exception {
+    setupMocksForCache(Constants.GROUPS_BYINCLUDE, EMPTY_JSON, SUCCESSFUL,
+        DO_NOT_THROW_EXCEPTION);
+    assertThat(restForwarder.evict(Constants.GROUPS_BYINCLUDE,
+        mock(AccountGroup.UUID.class))).isTrue();
+  }
+
+  @Test
+  public void testEvictGroupsMembersOK() throws Exception {
+    setupMocksForCache(Constants.GROUPS_MEMBERS, EMPTY_JSON, SUCCESSFUL,
+        DO_NOT_THROW_EXCEPTION);
+    assertThat(restForwarder.evict(Constants.GROUPS_MEMBERS,
+        mock(AccountGroup.UUID.class))).isTrue();
+  }
+
+  @Test
+  public void testEvictProjectListOK() throws Exception {
+    setupMocksForCache(Constants.PROJECT_LIST, EMPTY_JSON, SUCCESSFUL,
+        DO_NOT_THROW_EXCEPTION);
+    assertThat(restForwarder.evict(Constants.PROJECT_LIST, new Object()))
+        .isTrue();
+  }
+
+  @Test
+  public void testEvictCacheFailed() throws Exception {
+    setupMocksForCache(Constants.PROJECTS, EMPTY_JSON2, FAILED,
+        DO_NOT_THROW_EXCEPTION);
+    assertThat(restForwarder.evict(Constants.PROJECTS, EMPTY_JSON)).isFalse();
+  }
+
+  @Test
+  public void testEvictCacheThrowsException() throws Exception {
+    setupMocksForCache(Constants.PROJECTS, EMPTY_JSON2, FAILED,
+        THROW_EXCEPTION);
+    assertThat(restForwarder.evict(Constants.PROJECTS, EMPTY_JSON)).isFalse();
+  }
+
+  private void setupMocksForCache(String cacheName, String json,
+      boolean isOperationSuccessful, boolean exception) throws IOException {
+    String request =
+        Joiner.on("/").join("/plugins", PLUGIN_NAME, "cache", cacheName);
+    HttpSession httpSession = mock(HttpSession.class);
+    if (exception) {
+      doThrow(new IOException()).when(httpSession).post(request, json);
+    } else {
+      HttpResult result = new HttpResult(isOperationSuccessful, "Error");
+      when(httpSession.post(request, json)).thenReturn(result);
+    }
+    restForwarder = new RestForwarder(httpSession, PLUGIN_NAME);
+  }
+}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java
new file mode 100644
index 0000000..aa4c365
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java
@@ -0,0 +1,129 @@
+// Copyright (C) 2015 Ericsson
+//
+// 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 com.ericsson.gerrit.plugins.highavailability.index;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.git.WorkQueue.Executor;
+import com.google.gwtorm.client.KeyUtil;
+import com.google.gwtorm.server.StandardKeyEncoder;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Context;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
+import com.ericsson.gerrit.plugins.highavailability.index.IndexEventHandler.IndexTask;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class IndexEventHandlerTest {
+  private static final String PLUGIN_NAME = "high-availability";
+  private static final int CHANGE_ID = 1;
+
+  private IndexEventHandler indexEventHandler;
+  @Mock
+  private Forwarder forwarder;
+  private Change.Id id;
+
+  @BeforeClass
+  public static void setUp() {
+    KeyUtil.setEncoderImpl(new StandardKeyEncoder());
+  }
+
+  @Before
+  public void setUpMocks() {
+    id = Change.Id.parse(Integer.toString(CHANGE_ID));
+    indexEventHandler = new IndexEventHandler(MoreExecutors.directExecutor(),
+        PLUGIN_NAME, forwarder);
+  }
+
+  @Test
+  public void shouldIndexInRemoteOnChangeIndexedEvent() throws Exception {
+    indexEventHandler.onChangeIndexed(id.get());
+    verify(forwarder).indexChange(CHANGE_ID);
+  }
+
+  @Test
+  public void shouldDeleteFromIndexInRemoteOnChangeDeletedEvent()
+      throws Exception {
+    indexEventHandler.onChangeDeleted(id.get());
+    verify(forwarder).deleteChangeFromIndex(CHANGE_ID);
+  }
+
+  @Test
+  public void shouldNotCallRemoteWhenEventIsForwarded() throws Exception {
+    Context.setForwardedEvent(true);
+    indexEventHandler.onChangeIndexed(id.get());
+    indexEventHandler.onChangeDeleted(id.get());
+    Context.unsetForwardedEvent();
+    verifyZeroInteractions(forwarder);
+  }
+
+  @Test
+  public void duplicateEventOfAQueuedEventShouldGetDiscarded() {
+    Executor poolMock = mock(Executor.class);
+    indexEventHandler =
+        new IndexEventHandler(poolMock, PLUGIN_NAME, forwarder);
+    indexEventHandler.onChangeIndexed(id.get());
+    indexEventHandler.onChangeIndexed(id.get());
+    verify(poolMock, times(1))
+        .execute(indexEventHandler.new IndexTask(CHANGE_ID, false));
+  }
+
+  @Test
+  public void testIndexTaskToString() throws Exception {
+    IndexTask indexTask =
+        indexEventHandler.new IndexTask(CHANGE_ID, false);
+    assertThat(indexTask.toString()).isEqualTo(String.format(
+        "[%s] Index change %s in target instance", PLUGIN_NAME, CHANGE_ID));
+  }
+
+  @Test
+  public void testIndexTaskHashCodeAndEquals() {
+    IndexTask task = indexEventHandler.new IndexTask(CHANGE_ID, false);
+
+    assertThat(task.equals(task)).isTrue();
+    assertThat(task.hashCode()).isEqualTo(task.hashCode());
+
+    IndexTask identicalTask =
+        indexEventHandler.new IndexTask(CHANGE_ID, false);
+    assertThat(task.equals(identicalTask)).isTrue();
+    assertThat(task.hashCode()).isEqualTo(identicalTask.hashCode());
+
+    assertThat(task.equals(null)).isFalse();
+    assertThat(task.equals("test")).isFalse();
+    assertThat(task.hashCode()).isNotEqualTo("test".hashCode());
+
+    IndexTask differentChangeIdTask =
+        indexEventHandler.new IndexTask(123, false);
+    assertThat(task.equals(differentChangeIdTask)).isFalse();
+    assertThat(task.hashCode()).isNotEqualTo(differentChangeIdTask.hashCode());
+
+    IndexTask removeTask =
+        indexEventHandler.new IndexTask(CHANGE_ID, true);
+    assertThat(task.equals(removeTask)).isFalse();
+    assertThat(task.hashCode()).isNotEqualTo(removeTask.hashCode());
+  }
+}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheExecutorProviderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexExecutorProviderTest.java
similarity index 64%
rename from src/test/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheExecutorProviderTest.java
rename to src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexExecutorProviderTest.java
index a01b187..3db194b 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/evictcache/EvictCacheExecutorProviderTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexExecutorProviderTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.evictcache;
+package com.ericsson.gerrit.plugins.highavailability.index;
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Mockito.mock;
@@ -21,6 +21,8 @@
 
 import com.google.gerrit.server.git.WorkQueue;
 
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -28,37 +30,35 @@
 import org.mockito.junit.MockitoJUnitRunner;
 
 @RunWith(MockitoJUnitRunner.class)
-public class EvictCacheExecutorProviderTest {
-  private static final String PLUGIN_NAME = "evict-cache";
-
+public class IndexExecutorProviderTest {
   @Mock
   private WorkQueue.Executor executorMock;
-  private EvictCacheExecutorProvider evictCacheExecutorProvider;
+  private IndexExecutorProvider indexExecutorProvider;
 
   @Before
   public void setUp() throws Exception {
+    executorMock = mock(WorkQueue.Executor.class);
     WorkQueue workQueueMock = mock(WorkQueue.class);
-    when(workQueueMock.createQueue(4,
-        "Evict cache [" + PLUGIN_NAME + " plugin]")).thenReturn(executorMock);
+    when(workQueueMock.createQueue(4, "Forward-index-event"))
+        .thenReturn(executorMock);
     Configuration configMock = mock(Configuration.class);
-    when(configMock.getThreadPoolSize()).thenReturn(4);
-
-    evictCacheExecutorProvider =
-        new EvictCacheExecutorProvider(workQueueMock, PLUGIN_NAME, configMock);
+    when(configMock.getIndexThreadPoolSize()).thenReturn(4);
+    indexExecutorProvider =
+        new IndexExecutorProvider(workQueueMock, configMock);
   }
 
   @Test
   public void shouldReturnExecutor() throws Exception {
-    assertThat(evictCacheExecutorProvider.get()).isEqualTo(executorMock);
+    assertThat(indexExecutorProvider.get()).isEqualTo(executorMock);
   }
 
   @Test
   public void testStop() throws Exception {
-    evictCacheExecutorProvider.start();
-    assertThat(evictCacheExecutorProvider.get()).isEqualTo(executorMock);
-    evictCacheExecutorProvider.stop();
+    indexExecutorProvider.start();
+    assertThat(indexExecutorProvider.get()).isEqualTo(executorMock);
+    indexExecutorProvider.stop();
     verify(executorMock).shutdown();
     verify(executorMock).unregisterWorkQueue();
-    assertThat(evictCacheExecutorProvider.get()).isNull();
+    assertThat(indexExecutorProvider.get()).isNull();
   }
 }