Merge branch 'sync-events-plugin'

* sync-events-plugin:
  Replace EasyMock with Mockito
  Build with API version 2.13
  Fix buck build, remove java_test.source_under_test
  Change docs links to actual file extension (.md)
  Reduce visibility of SyncEventBroker to package-private
  Return proper http status code for unsupported media type
  Handle exceptions when sending error message in servlet
  Stop retrying if thread is interrupted
  Adjust to updated EventDispatcher interface
  Initial version of sync-events plugin

sync-event-plugin branch was created by fetching stable-2.13
(0e602d4bd2938c7b48e7b419abd11511241afbba) branch of repository
https://gerrit.googlesource.com/plugins/sync-events and pushing it to
this repository.

This merge adds the second of the 4 plugins that will be merged in this
repository. evict-cache and websession-flatfile will be done in follow
up commits.

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 sync-events features/bug fixes are
in stable-2.13, use this branch so high-availability will support Gerrit
2.13.

Change-Id: Ifde21b9607acc3aeb34f85ea9f3a560a3991b56d
diff --git a/.buckconfig b/.buckconfig
index dd9e0cf..5c0ead0 100644
--- a/.buckconfig
+++ b/.buckconfig
@@ -1,13 +1,15 @@
 [alias]
-  sync-events = //:sync-events
-  plugin = //:sync-events
-  src = //:sync-events-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 175600f..c27e17f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-/.buckd
+/.buckd/
 /.buckversion
 /.classpath
 /.project
diff --git a/BUCK b/BUCK
index 523c272..c888d36 100644
--- a/BUCK
+++ b/BUCK
@@ -9,34 +9,30 @@
   ':wiremock',
 ]
 
-PROVIDED_DEPS = GERRIT_TESTS + [
-  '//lib:gson',
-]
-
-TEST_DEPS = GERRIT_PLUGIN_API + PROVIDED_DEPS + DEPS + [
-  ':sync-events__plugin',
+TEST_DEPS = GERRIT_PLUGIN_API + GERRIT_TESTS + DEPS + [
+  ':high-availability__plugin',
   ':mockito',
 ]
 
 gerrit_plugin(
-  name = 'sync-events',
+  name = 'high-availability',
   srcs = SOURCES,
   resources = RESOURCES,
   manifest_entries = [
-    'Gerrit-PluginName: sync-events',
+    'Gerrit-PluginName: high-availability',
     'Gerrit-ApiType: plugin',
-    'Gerrit-Module: com.ericsson.gerrit.plugins.syncevents.Module',
-    'Gerrit-HttpModule: com.ericsson.gerrit.plugins.syncevents.HttpModule',
-    'Implementation-Title: sync-events plugin',
-    'Implementation-URL: https://gerrit-review.googlesource.com/#/admin/projects/plugins/sync-events',
+    '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,
+  provided_deps = GERRIT_TESTS,
   deps = DEPS,
 )
 
 java_sources(
-  name = 'sync-events-sources',
+  name = 'high-availability-sources',
   srcs = SOURCES + RESOURCES,
 )
 
@@ -46,9 +42,9 @@
 )
 
 java_test(
-  name = 'sync-events_tests',
+  name = 'high-availability_tests',
   srcs = glob(['src/test/java/**/*.java']),
-  labels = ['sync-events'],
+  labels = ['high-availability'],
   deps = TEST_DEPS,
 )
 
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 31eedbd..8a21820 100644
--- a/lib/gerrit/BUCK
+++ b/lib/gerrit/BUCK
@@ -1,21 +1,21 @@
 include_defs('//bucklets/maven_jar.bucklet')
 
-VER = '2.13'
+VER = '2.13.6'
 REPO = MAVEN_CENTRAL
 
 maven_jar(
-  name = 'plugin-api',
-  id = 'com.google.gerrit:gerrit-plugin-api:' + VER,
-  sha1 = 'e25d55b8f41627c4ae6b9d2069ec398638b219a3',
+  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 = 'a6913a61196a8fccdb45e761f43a0b7e21867c90',
+  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/syncevents/Configuration.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
similarity index 75%
rename from src/main/java/com/ericsson/gerrit/plugins/syncevents/Configuration.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
index 01a6db9..bd1caea 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/syncevents/Configuration.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.syncevents;
+package com.ericsson.gerrit.plugins.highavailability;
 
 import com.google.common.base.CharMatcher;
 import com.google.common.base.Strings;
@@ -23,7 +23,7 @@
 import com.google.inject.Singleton;
 
 @Singleton
-class Configuration {
+public 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;
@@ -36,7 +36,8 @@
   private final int socketTimeout;
   private final int maxTries;
   private final int retryInterval;
-  private final int threadPoolSize;
+  private final int indexThreadPoolSize;
+  private final int eventThreadPoolSize;
 
   @Inject
   Configuration(PluginConfigFactory config,
@@ -49,38 +50,45 @@
     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);
+    indexThreadPoolSize =
+        cfg.getInt("indexThreadPoolSize", DEFAULT_THREAD_POOL_SIZE);
+    eventThreadPoolSize =
+        cfg.getInt("eventThreadPoolSize", DEFAULT_THREAD_POOL_SIZE);
   }
 
-  int getConnectionTimeout() {
+  public int getConnectionTimeout() {
     return connectionTimeout;
   }
 
-  int getMaxTries() {
+  public int getMaxTries() {
     return maxTries;
   }
 
-  int getRetryInterval() {
+  public int getRetryInterval() {
     return retryInterval;
   }
 
-  int getSocketTimeout() {
+  public int getSocketTimeout() {
     return socketTimeout;
   }
 
-  String getUrl() {
+  public String getUrl() {
     return CharMatcher.is('/').trimTrailingFrom(url);
   }
 
-  String getUser() {
+  public String getUser() {
     return user;
   }
 
-  String getPassword() {
+  public String getPassword() {
     return password;
   }
 
-  int getThreadPoolSize() {
-    return threadPoolSize;
+  public int getIndexThreadPoolSize() {
+    return indexThreadPoolSize;
+  }
+
+  public int getEventThreadPoolSize() {
+    return eventThreadPoolSize;
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/syncevents/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/syncevents/HttpModule.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/HttpModule.java
index 58cc1fc..b221f67 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/syncevents/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.syncevents;
+package com.ericsson.gerrit.plugins.highavailability;
 
 import com.google.gerrit.httpd.plugins.HttpPluginModule;
 
+import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.EventForwarderServletModule;
+
 class HttpModule extends HttpPluginModule {
   @Override
   protected void configureServlets() {
-    serve("/event").with(SyncEventsRestApiServlet.class);
+    install(new EventForwarderServletModule());
   }
 }
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..fe22da2
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java
@@ -0,0 +1,33 @@
+// 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.event.EventModule;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.RestEventForwarderModule;
+import com.ericsson.gerrit.plugins.highavailability.index.IndexModule;
+
+class Module extends LifecycleModule {
+
+  @Override
+  protected void configure() {
+    bind(Configuration.class).in(Scopes.SINGLETON);
+    install(new RestEventForwarderModule());
+    install(new EventModule());
+    install(new IndexModule());
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/syncevents/SyncEventExecutor.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/syncevents/SyncEventExecutor.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventExecutor.java
index 841be21..b204220 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/syncevents/SyncEventExecutor.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.syncevents;
+package com.ericsson.gerrit.plugins.highavailability.event;
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
@@ -22,5 +22,5 @@
 
 @Retention(RUNTIME)
 @BindingAnnotation
-@interface SyncEventExecutor {
+@interface EventExecutor {
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/syncevents/SyncEventExecutorProvider.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/syncevents/SyncEventExecutorProvider.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventExecutorProvider.java
index 3ddec77..6a6c3c3 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/syncevents/SyncEventExecutorProvider.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.syncevents;
+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 SyncEventExecutorProvider
+class EventExecutorProvider
     implements Provider<ScheduledThreadPoolExecutor>, LifecycleListener {
   private WorkQueue.Executor executor;
 
   @Inject
-  SyncEventExecutorProvider(WorkQueue workQueue,
-      @PluginName String pluginName,
+  EventExecutorProvider(WorkQueue workQueue,
       Configuration config) {
-    executor = workQueue.createQueue(config.getThreadPoolSize(),
-        "Sync stream events [" + pluginName + " plugin]");
+    executor = workQueue.createQueue(config.getEventThreadPoolSize(),
+        "Forward-stream-event");
   }
 
   @Override
diff --git a/src/main/java/com/ericsson/gerrit/plugins/syncevents/EventHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandler.java
similarity index 73%
rename from src/main/java/com/ericsson/gerrit/plugins/syncevents/EventHandler.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandler.java
index 73f8708..fb7b2f4 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/syncevents/EventHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandler.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.syncevents;
+package com.ericsson.gerrit.plugins.highavailability.event;
 
 import com.google.gerrit.common.EventListener;
 import com.google.gerrit.extensions.annotations.PluginName;
@@ -20,18 +20,21 @@
 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.EventForwarder;
+
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 
 class EventHandler implements EventListener {
   private final ScheduledThreadPoolExecutor executor;
-  private final RestSession restClient;
+  private final EventForwarder eventForwarder;
   private final String pluginName;
 
   @Inject
-  EventHandler(RestSession restClient,
-      @SyncEventExecutor ScheduledThreadPoolExecutor executor,
+  EventHandler(EventForwarder eventForwarder,
+      @EventExecutor ScheduledThreadPoolExecutor executor,
       @PluginName String pluginName) {
-    this.restClient = restClient;
+    this.eventForwarder = eventForwarder;
     this.executor = executor;
     this.pluginName = pluginName;
   }
@@ -39,20 +42,20 @@
   @Override
   public void onEvent(Event event) {
     if (!Context.isForwardedEvent() && event instanceof ProjectEvent) {
-      executor.execute(new SyncEventTask(event));
+      executor.execute(new EventTask(event));
     }
   }
 
-  class SyncEventTask implements Runnable {
+  class EventTask implements Runnable {
     private Event event;
 
-    SyncEventTask(Event event) {
+    EventTask(Event event) {
       this.event = event;
     }
 
     @Override
     public void run() {
-      restClient.send(event);
+      eventForwarder.send(event);
     }
 
     @Override
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/syncevents/SyncEventBroker.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/ForwardedAwareEventBroker.java
similarity index 84%
rename from src/main/java/com/ericsson/gerrit/plugins/syncevents/SyncEventBroker.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/event/ForwardedAwareEventBroker.java
index 36cfc15..e099343 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/syncevents/SyncEventBroker.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/ForwardedAwareEventBroker.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.syncevents;
+package com.ericsson.gerrit.plugins.highavailability.event;
 
 import com.google.gerrit.common.EventBroker;
 import com.google.gerrit.common.EventListener;
@@ -25,10 +25,12 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
-class SyncEventBroker extends EventBroker {
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Context;
+
+class ForwardedAwareEventBroker extends EventBroker {
 
   @Inject
-  SyncEventBroker(DynamicSet<UserScopedEventListener> listeners,
+  ForwardedAwareEventBroker(DynamicSet<UserScopedEventListener> listeners,
       DynamicSet<EventListener> unrestrictedListeners,
       ProjectCache projectCache,
       Factory notesFactory,
diff --git a/src/main/java/com/ericsson/gerrit/plugins/syncevents/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/syncevents/Context.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Context.java
index ba677c9..3651e73 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/syncevents/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.syncevents;
+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/EventForwarder.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/EventForwarder.java
new file mode 100644
index 0000000..75c76ef
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/EventForwarder.java
@@ -0,0 +1,47 @@
+// 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 event to the other master
+ */
+public interface EventForwarder {
+
+  /**
+   * 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 an event to the other master.
+   *
+   * @param event the event to forward.
+   * @return true if successful, otherwise false.
+   */
+  boolean send(Event event);
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/syncevents/HttpModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventForwarderServletModule.java
similarity index 69%
copy from src/main/java/com/ericsson/gerrit/plugins/syncevents/HttpModule.java
copy to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventForwarderServletModule.java
index 58cc1fc..ffc9d0e 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/syncevents/HttpModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventForwarderServletModule.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,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.syncevents;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import com.google.gerrit.httpd.plugins.HttpPluginModule;
 
-class HttpModule extends HttpPluginModule {
+public class EventForwarderServletModule extends HttpPluginModule {
   @Override
   protected void configureServlets() {
-    serve("/event").with(SyncEventsRestApiServlet.class);
+    serveRegex("/index/\\d+$").with(IndexRestApiServlet.class);
+    serve("/event").with(EventRestApiServlet.class);
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/syncevents/SyncEventsRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java
similarity index 90%
rename from src/main/java/com/ericsson/gerrit/plugins/syncevents/SyncEventsRestApiServlet.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java
index eae220b..eb8e53e 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/syncevents/SyncEventsRestApiServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.syncevents;
+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;
@@ -33,6 +33,8 @@
 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;
 
@@ -44,15 +46,15 @@
 import javax.servlet.http.HttpServletResponse;
 
 @Singleton
-class SyncEventsRestApiServlet extends HttpServlet {
+class EventRestApiServlet extends HttpServlet {
   private static final long serialVersionUID = -1L;
   private static final Logger logger = LoggerFactory
-      .getLogger(SyncEventsRestApiServlet.class);
+      .getLogger(EventRestApiServlet.class);
 
   private final EventDispatcher dispatcher;
 
   @Inject
-  SyncEventsRestApiServlet(EventDispatcher dispatcher) {
+  EventRestApiServlet(EventDispatcher dispatcher) {
     this.dispatcher = dispatcher;
   }
 
@@ -62,7 +64,7 @@
     rsp.setContentType("text/plain");
     rsp.setCharacterEncoding("UTF-8");
     try {
-      Context.setForwardedEvent();
+      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");
diff --git a/src/main/java/com/ericsson/gerrit/plugins/syncevents/SyncEventExecutor.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/syncevents/SyncEventExecutor.java
copy to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ForwardUrl.java
index 841be21..72ab757 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/syncevents/SyncEventExecutor.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.syncevents;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
@@ -22,5 +22,5 @@
 
 @Retention(RUNTIME)
 @BindingAnnotation
-@interface SyncEventExecutor {
+@interface ForwardUrl {
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/syncevents/HttpClientProvider.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpClientProvider.java
similarity index 92%
rename from src/main/java/com/ericsson/gerrit/plugins/syncevents/HttpClientProvider.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpClientProvider.java
index 95821ce..7e4eb28 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/syncevents/HttpClientProvider.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpClientProvider.java
@@ -12,11 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.syncevents;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
+
 import org.apache.http.HttpResponse;
 import org.apache.http.auth.AuthScope;
 import org.apache.http.auth.UsernamePasswordCredentials;
@@ -28,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;
@@ -43,10 +46,8 @@
 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;
 
@@ -54,8 +55,8 @@
  * Provides an HTTP client with SSL capabilities.
  */
 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;
@@ -135,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() {
@@ -152,7 +153,7 @@
 
   private SSLConnectionSocketFactory buildSslSocketFactory() {
     return new SSLConnectionSocketFactory(buildSslContext(),
-        new DummyHostnameVerifier());
+        NoopHostnameVerifier.INSTANCE);
   }
 
   private SSLContext buildSslContext() {
@@ -192,12 +193,4 @@
       // no check
     }
   }
-
-  private static class DummyHostnameVerifier implements HostnameVerifier {
-    @Override
-    public boolean verify(String hostname, SSLSession session) {
-      // always accept
-      return true;
-    }
-  }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/syncevents/SyncEventsResponseHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpResponseHandler.java
similarity index 75%
rename from src/main/java/com/ericsson/gerrit/plugins/syncevents/SyncEventsResponseHandler.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpResponseHandler.java
index 59cebf3..ad98f2e 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/syncevents/SyncEventsResponseHandler.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.syncevents;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
 
-import com.ericsson.gerrit.plugins.syncevents.SyncEventsResponseHandler.SyncResult;
+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 SyncEventsResponseHandler implements ResponseHandler<SyncResult> {
+class HttpResponseHandler implements ResponseHandler<HttpResult> {
 
-  static class SyncResult {
+  static class HttpResult {
     private boolean successful;
     private String message;
 
-    SyncResult(boolean successful, String message) {
+    HttpResult(boolean successful, String message) {
       this.successful = successful;
       this.message = message;
     }
@@ -47,12 +47,12 @@
     }
   }
 
-  private static final Logger log = LoggerFactory
-      .getLogger(SyncEventsResponseHandler.class);
+  private static final Logger log =
+      LoggerFactory.getLogger(HttpResponseHandler.class);
 
   @Override
-  public SyncResult handleResponse(HttpResponse response) {
-    return new SyncResult(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/syncevents/HttpSession.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java
similarity index 68%
rename from src/main/java/com/ericsson/gerrit/plugins/syncevents/HttpSession.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java
index b1c32e5..574dd82 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/syncevents/HttpSession.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java
@@ -12,14 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.syncevents;
+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.syncevents.SyncEventsResponseHandler.SyncResult;
+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;
@@ -33,17 +34,26 @@
 
   @Inject
   HttpSession(CloseableHttpClient httpClient,
-      @SyncUrl String url) {
+      @ForwardUrl String url) {
     this.httpClient = httpClient;
     this.url = url;
   }
 
-  SyncResult post(String endpoint, String content) throws IOException {
+  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 SyncEventsResponseHandler());
+    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/RestEventForwarder.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestEventForwarder.java
new file mode 100644
index 0000000..d81b9fe
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestEventForwarder.java
@@ -0,0 +1,100 @@
+// 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.EventForwarder;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.HttpResponseHandler.HttpResult;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+class RestEventForwarder implements EventForwarder {
+  private static final Logger log =
+      LoggerFactory.getLogger(RestEventForwarder.class);
+
+  private final HttpSession httpSession;
+  private final String pluginName;
+
+  @Inject
+  RestEventForwarder(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;
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestEventForwarderModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestEventForwarderModule.java
new file mode 100644
index 0000000..e47ab8d
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestEventForwarderModule.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.EventForwarder;
+
+import org.apache.http.impl.client.CloseableHttpClient;
+
+public class RestEventForwarderModule extends LifecycleModule {
+
+  @Override
+  protected void configure() {
+    bind(CloseableHttpClient.class).toProvider(HttpClientProvider.class)
+        .in(Scopes.SINGLETON);
+    bind(HttpSession.class);
+    bind(EventForwarder.class).to(RestEventForwarder.class);
+  }
+
+  @Provides
+  @ForwardUrl
+  String forwardUrl(Configuration config) {
+    return config.getUrl();
+  }
+}
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..566e81a
--- /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.EventForwarder;
+
+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 EventForwarder eventForwarder;
+  private final String pluginName;
+  private final Set<IndexTask> queuedTasks = Collections
+      .newSetFromMap(new ConcurrentHashMap<IndexTask, Boolean>());
+
+  @Inject
+  IndexEventHandler(@IndexExecutor Executor executor,
+      @PluginName String pluginName,
+      EventForwarder eventForwarder) {
+    this.eventForwarder = eventForwarder;
+    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) {
+        eventForwarder.deleteChangeFromIndex(changeId);
+      } else {
+        eventForwarder.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/syncevents/SyncEventExecutor.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/syncevents/SyncEventExecutor.java
copy to src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexExecutor.java
index 841be21..e6c20bd 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/syncevents/SyncEventExecutor.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.syncevents;
+package com.ericsson.gerrit.plugins.highavailability.index;
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
@@ -22,5 +22,5 @@
 
 @Retention(RUNTIME)
 @BindingAnnotation
-@interface SyncEventExecutor {
+@interface IndexExecutor {
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/syncevents/SyncEventExecutorProvider.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexExecutorProvider.java
similarity index 64%
copy from src/main/java/com/ericsson/gerrit/plugins/syncevents/SyncEventExecutorProvider.java
copy to src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexExecutorProvider.java
index 3ddec77..73aa3be 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/syncevents/SyncEventExecutorProvider.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.syncevents;
+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 SyncEventExecutorProvider
-    implements Provider<ScheduledThreadPoolExecutor>, LifecycleListener {
+class IndexExecutorProvider implements Provider<Executor>,
+    LifecycleListener {
   private WorkQueue.Executor executor;
 
   @Inject
-  SyncEventExecutorProvider(WorkQueue workQueue,
-      @PluginName String pluginName,
-      Configuration config) {
-    executor = workQueue.createQueue(config.getThreadPoolSize(),
-        "Sync stream events [" + 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/java/com/ericsson/gerrit/plugins/syncevents/Module.java b/src/main/java/com/ericsson/gerrit/plugins/syncevents/Module.java
deleted file mode 100644
index 39d9453..0000000
--- a/src/main/java/com/ericsson/gerrit/plugins/syncevents/Module.java
+++ /dev/null
@@ -1,51 +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.syncevents;
-
-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 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(SyncEventExecutor.class)
-        .toProvider(SyncEventExecutorProvider.class);
-    listener().to(SyncEventExecutorProvider.class);
-    DynamicSet.bind(binder(), EventListener.class).to(EventHandler.class);
-    DynamicItem.bind(binder(), EventDispatcher.class).to(SyncEventBroker.class);
-  }
-
-  @Provides
-  @SyncUrl
-  String syncUrl(Configuration config) {
-    return config.getUrl();
-  }
-}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/syncevents/RestSession.java b/src/main/java/com/ericsson/gerrit/plugins/syncevents/RestSession.java
deleted file mode 100644
index 94e1b90..0000000
--- a/src/main/java/com/ericsson/gerrit/plugins/syncevents/RestSession.java
+++ /dev/null
@@ -1,64 +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.syncevents;
-
-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.syncevents.SyncEventsResponseHandler.SyncResult;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-
-class RestSession {
-  private static final Logger log = LoggerFactory.getLogger(RestSession.class);
-  private final HttpSession httpSession;
-  private final String pluginName;
-
-  @Inject
-  RestSession(HttpSession httpClient,
-      @PluginName String pluginName) {
-    this.httpSession = httpClient;
-    this.pluginName = pluginName;
-  }
-
-  boolean send(Event event) {
-    String serializedEvent = new GsonBuilder()
-        .registerTypeAdapter(Supplier.class, new SupplierSerializer()).create()
-        .toJson(event);
-    try {
-      SyncResult result = httpSession.post(buildEndpoint(), 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;
-  }
-
-  private String buildEndpoint() {
-    return Joiner.on("/").join("/plugins", pluginName, "event");
-  }
-}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/syncevents/SyncUrl.java b/src/main/java/com/ericsson/gerrit/plugins/syncevents/SyncUrl.java
deleted file mode 100644
index 851aec8..0000000
--- a/src/main/java/com/ericsson/gerrit/plugins/syncevents/SyncUrl.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.syncevents;
-
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import com.google.inject.BindingAnnotation;
-
-import java.lang.annotation.Retention;
-
-@Retention(RUNTIME)
-@BindingAnnotation
-@interface SyncUrl {
-}
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index e12f31a..02db878 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -1,13 +1,17 @@
-The @PLUGIN@ plugin allows to share stream events between two Gerrit instances
-sharing the same git repositories and database.
+The @PLUGIN@ plugin allows to synchronize 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 stream event occurs in
-one of the instances (see [more events info]
+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.
+
+Eevery 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.
+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.
+[config](config.md) documentation.
\ No newline at end of file
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index f01df3f..f6c1ccc 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -7,6 +7,11 @@
 File 'gerrit.config'
 --------------------
 
+[plugin "@PLUGIN@"]
+:  url = target_instance_url
+:  user = username
+:  password = password
+
 plugin.@PLUGIN@.url
 :   Specify the URL for the secondary (target) instance.
 
@@ -15,9 +20,9 @@
 
 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 send events to the target instance. It
+@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:
 
@@ -31,15 +36,19 @@
     the default value is set to 5000ms.
 
 @PLUGIN@.maxTries
-:   Maximum number of times the plugin should attempt to send the event to 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.
+:   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
+@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.
diff --git a/src/test/java/com/ericsson/gerrit/plugins/syncevents/ConfigurationTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
similarity index 85%
rename from src/test/java/com/ericsson/gerrit/plugins/syncevents/ConfigurationTest.java
rename to src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
index f14df56..10b4211 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/syncevents/ConfigurationTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.syncevents;
+package com.ericsson.gerrit.plugins.highavailability;
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Mockito.when;
@@ -20,6 +20,8 @@
 import com.google.gerrit.server.config.PluginConfig;
 import com.google.gerrit.server.config.PluginConfigFactory;
 
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -44,7 +46,7 @@
   @Mock
   private PluginConfig configMock;
   private Configuration configuration;
-  private String pluginName = "sync-events";
+  private String pluginName = "multi-master";
 
   @Before
   public void setUp() throws Exception {
@@ -62,7 +64,10 @@
     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);
   }
 
   @Test
@@ -75,7 +80,8 @@
     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);
   }
 
   @Test
@@ -99,7 +105,9 @@
         .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);
 
     configuration = new Configuration(cfgFactoryMock, pluginName);
diff --git a/src/test/java/com/ericsson/gerrit/plugins/syncevents/SyncEventExecutorProviderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventExecutorProviderTest.java
similarity index 65%
rename from src/test/java/com/ericsson/gerrit/plugins/syncevents/SyncEventExecutorProviderTest.java
rename to src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventExecutorProviderTest.java
index 676e028..085ca0a 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/syncevents/SyncEventExecutorProviderTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventExecutorProviderTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.syncevents;
+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,35 +31,35 @@
 import org.mockito.junit.MockitoJUnitRunner;
 
 @RunWith(MockitoJUnitRunner.class)
-public class SyncEventExecutorProviderTest {
+public class EventExecutorProviderTest {
   @Mock
   private WorkQueue.Executor executorMock;
-  private SyncEventExecutorProvider syncEventsExecutorProvider;
+  private EventExecutorProvider eventsExecutorProvider;
 
   @Before
   public void setUp() throws Exception {
     WorkQueue workQueueMock = mock(WorkQueue.class);
-    when(workQueueMock.createQueue(4, "Sync stream events [SyncEvents plugin]"))
+    when(workQueueMock.createQueue(4, "Forward-stream-event"))
         .thenReturn(executorMock);
     Configuration configMock = mock(Configuration.class);
-    when(configMock.getThreadPoolSize()).thenReturn(4);
+    when(configMock.getEventThreadPoolSize()).thenReturn(4);
 
-    syncEventsExecutorProvider =
-        new SyncEventExecutorProvider(workQueueMock, "SyncEvents", configMock);
+    eventsExecutorProvider =
+        new EventExecutorProvider(workQueueMock, configMock);
   }
 
   @Test
   public void shouldReturnExecutor() throws Exception {
-    assertThat(syncEventsExecutorProvider.get()).isEqualTo(executorMock);
+    assertThat(eventsExecutorProvider.get()).isEqualTo(executorMock);
   }
 
   @Test
   public void testStop() throws Exception {
-    syncEventsExecutorProvider.start();
-    assertThat(syncEventsExecutorProvider.get()).isEqualTo(executorMock);
-    syncEventsExecutorProvider.stop();
+    eventsExecutorProvider.start();
+    assertThat(eventsExecutorProvider.get()).isEqualTo(executorMock);
+    eventsExecutorProvider.stop();
     verify(executorMock).shutdown();
     verify(executorMock).unregisterWorkQueue();
-    assertThat(syncEventsExecutorProvider.get()).isNull();
+    assertThat(eventsExecutorProvider.get()).isNull();
   }
 }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/syncevents/EventHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandlerTest.java
similarity index 77%
rename from src/test/java/com/ericsson/gerrit/plugins/syncevents/EventHandlerTest.java
rename to src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandlerTest.java
index b84be73..91b20eb 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/syncevents/EventHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandlerTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.syncevents;
+package com.ericsson.gerrit.plugins.highavailability.event;
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Mockito.mock;
@@ -22,6 +22,10 @@
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.RefEvent;
 
+import com.ericsson.gerrit.plugins.highavailability.event.EventHandler;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Context;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.EventForwarder;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -31,43 +35,43 @@
 
 @RunWith(MockitoJUnitRunner.class)
 public class EventHandlerTest {
-  private static final String PLUGIN_NAME = "sync-event";
+  private static final String PLUGIN_NAME = "multi-master";
 
   private Event event;
   private EventHandler eventHandler;
   @Mock
-  private RestSession restSession;
+  private EventForwarder eventForwarder;
 
   @Test
   public void testRightEventAndNotForwarded() throws Exception {
     setUpMocks(true);
     eventHandler.onEvent(event);
-    verify(restSession).send(event);
+    verify(eventForwarder).send(event);
   }
 
   @Test
   public void testRightEventIsForwarded() throws Exception {
     setUpMocks(true);
-    Context.setForwardedEvent();
+    Context.setForwardedEvent(true);
     eventHandler.onEvent(event);
     Context.unsetForwardedEvent();
-    verifyZeroInteractions(restSession);
+    verifyZeroInteractions(eventForwarder);
   }
 
   @Test
   public void testBadEventAndNotForwarded() throws Exception {
     setUpMocks(false);
     eventHandler.onEvent(event);
-    verifyZeroInteractions(restSession);
+    verifyZeroInteractions(eventForwarder);
   }
 
   @Test
   public void testBadEventAndItIsForwarded() throws Exception {
     setUpMocks(false);
-    Context.setForwardedEvent();
+    Context.setForwardedEvent(true);
     eventHandler.onEvent(event);
     Context.unsetForwardedEvent();
-    verifyZeroInteractions(restSession);
+    verifyZeroInteractions(eventForwarder);
   }
 
   private void setUpMocks(boolean rightEvent) {
@@ -77,7 +81,7 @@
     } else {
       event = mock(Event.class);
     }
-    eventHandler = new EventHandler(restSession, pool, PLUGIN_NAME);
+    eventHandler = new EventHandler(eventForwarder, pool, PLUGIN_NAME);
   }
 
   private class PoolMock extends ScheduledThreadPoolExecutor {
diff --git a/src/test/java/com/ericsson/gerrit/plugins/syncevents/SyncEventBrokerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/ForwardedAwareEventBrokerTest.java
similarity index 78%
rename from src/test/java/com/ericsson/gerrit/plugins/syncevents/SyncEventBrokerTest.java
rename to src/test/java/com/ericsson/gerrit/plugins/highavailability/event/ForwardedAwareEventBrokerTest.java
index 1ca7170..713f7ef 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/syncevents/SyncEventBrokerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/ForwardedAwareEventBrokerTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.syncevents;
+package com.ericsson.gerrit.plugins.highavailability.event;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -22,13 +22,16 @@
 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 SyncEventBrokerTest {
+public class ForwardedAwareEventBrokerTest {
 
   private EventListener listenerMock;
-  private SyncEventBroker broker;
+  private ForwardedAwareEventBroker broker;
   private Event event = new Event(null) {};
 
   @Before
@@ -36,7 +39,7 @@
     listenerMock = mock(EventListener.class);
     DynamicSet<EventListener> listeners = DynamicSet.emptySet();
     listeners.add(listenerMock);
-    broker = new SyncEventBroker(null, listeners, null, null, null);
+    broker = new ForwardedAwareEventBroker(null, listeners, null, null, null);
   }
 
   @Test
@@ -47,7 +50,7 @@
 
   @Test
   public void shouldNotDispatchForwardedEvents() {
-    Context.setForwardedEvent();
+    Context.setForwardedEvent(true);
     try {
       broker.fireEventForUnrestrictedListeners(event);
     } finally {
diff --git a/src/test/java/com/ericsson/gerrit/plugins/syncevents/SyncEventsRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServletTest.java
similarity index 86%
rename from src/test/java/com/ericsson/gerrit/plugins/syncevents/SyncEventsRestApiServletTest.java
rename to src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServletTest.java
index 5c318c8..27d46b7 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/syncevents/SyncEventsRestApiServletTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServletTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.syncevents;
+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;
@@ -33,6 +33,8 @@
 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;
@@ -48,7 +50,7 @@
 import javax.servlet.http.HttpServletResponse;
 
 @RunWith(MockitoJUnitRunner.class)
-public class SyncEventsRestApiServletTest {
+public class EventRestApiServletTest {
   private static final String ERR_MSG = "some Error";
 
   @Mock
@@ -57,7 +59,7 @@
   private HttpServletRequest req;
   @Mock
   private HttpServletResponse rsp;
-  private SyncEventsRestApiServlet syncEventsRestApiServlet;
+  private EventRestApiServlet eventRestApiServlet;
 
   @BeforeClass
   public static void setup() {
@@ -67,40 +69,40 @@
   }
 
   @Before
-  public void createSyncEventsRestApiServlet() throws Exception {
-    syncEventsRestApiServlet = new SyncEventsRestApiServlet(dispatcher);
+  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/test-sync-index\",\"ref\":"
+    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));
-    syncEventsRestApiServlet.doPost(req, rsp);
+    eventRestApiServlet.doPost(req, rsp);
     verify(rsp).setStatus(SC_NO_CONTENT);
   }
 
   @Test
   public void testDoPostDispatcherFailure() throws Exception {
-    String event = "{\"project\":\"gerrit/test-sync-index\",\"ref\":"
+    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));
-    syncEventsRestApiServlet.doPost(req, rsp);
+    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();
-    syncEventsRestApiServlet.doPost(req, rsp);
+    eventRestApiServlet.doPost(req, rsp);
     verify(rsp).sendError(SC_BAD_REQUEST, ERR_MSG);
   }
 
@@ -108,7 +110,7 @@
   public void testDoPostWrongMediaType() throws Exception {
     when(req.getContentType())
         .thenReturn(MediaType.APPLICATION_XML_UTF_8.toString());
-    syncEventsRestApiServlet.doPost(req, rsp);
+    eventRestApiServlet.doPost(req, rsp);
     verify(rsp).sendError(SC_UNSUPPORTED_MEDIA_TYPE,
         "Expecting " + JSON_UTF_8.toString() + " content type");
   }
@@ -118,7 +120,7 @@
     doThrow(new IOException(ERR_MSG)).when(req).getReader();
     doThrow(new IOException("someOtherError")).when(rsp)
         .sendError(SC_BAD_REQUEST, ERR_MSG);
-    syncEventsRestApiServlet.doPost(req, rsp);
+    eventRestApiServlet.doPost(req, rsp);
   }
 
   static class RefReplicationDoneEvent extends RefEvent {
diff --git a/src/test/java/com/ericsson/gerrit/plugins/syncevents/HttpClientProviderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpClientProviderTest.java
similarity index 93%
rename from src/test/java/com/ericsson/gerrit/plugins/syncevents/HttpClientProviderTest.java
rename to src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpClientProviderTest.java
index 30b4cf2..395c110 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/syncevents/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.syncevents;
+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;
diff --git a/src/test/java/com/ericsson/gerrit/plugins/syncevents/SyncEventsResponseHandlerTest.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/syncevents/SyncEventsResponseHandlerTest.java
rename to src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpResponseHandlerTest.java
index e17e283..b612770 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/syncevents/SyncEventsResponseHandlerTest.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.syncevents;
+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.syncevents.SyncEventsResponseHandler.SyncResult;
+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 SyncEventsResponseHandlerTest {
+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 SyncEventsResponseHandler handler;
+  private HttpResponseHandler handler;
 
   @Before
   public void setUp() throws Exception {
-    handler = new SyncEventsResponseHandler();
+    handler = new HttpResponseHandler();
   }
 
   @Test
   public void testIsSuccessful() throws Exception {
     HttpResponse response = setupMocks(OK, EMPTY_ENTITY);
-    SyncResult 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);
-    SyncResult 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/highavailability/forwarder/rest/HttpSessionTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSessionTest.java
new file mode 100644
index 0000000..4ba6fe1
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSessionTest.java
@@ -0,0 +1,209 @@
+// 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.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.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.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.ClassRule;
+import org.junit.Test;
+
+import java.net.SocketTimeoutException;
+
+public class HttpSessionTest {
+  private static final int MAX_TRIES = 3;
+  private static final int RETRY_INTERVAL = 250;
+  private static final int TIMEOUT = 500;
+  private static final int ERROR = 500;
+  private static final int OK = 204;
+  private static final int NOT_FOUND = 404;
+  private static final int UNAUTHORIZED = 401;
+
+  private static final String ENDPOINT = "/plugins/multi-master/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";
+  private static final String THIRD_TRY = "Third try";
+  private static final String RETRY_AT_ERROR = "Retry at error";
+  private static final String RETRY_AT_DELAY = "Retry at delay";
+
+  private HttpSession httpSession;
+
+  @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(url);
+    when(cfg.getUser()).thenReturn("user");
+    when(cfg.getPassword()).thenReturn("pass");
+    when(cfg.getMaxTries()).thenReturn(MAX_TRIES);
+    when(cfg.getConnectionTimeout()).thenReturn(TIMEOUT);
+    when(cfg.getSocketTimeout()).thenReturn(TIMEOUT);
+    when(cfg.getRetryInterval()).thenReturn(RETRY_INTERVAL);
+
+    httpSession =
+        new HttpSession(new HttpClientProvider(cfg).get(), url);
+    wireMockRule.resetRequests();
+  }
+
+  @Test
+  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";
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT))
+        .willReturn(aResponse().withStatus(UNAUTHORIZED).withBody(expected)));
+
+    HttpResult result = httpSession.post(ENDPOINT);
+    assertThat(result.isSuccessful()).isFalse();
+    assertThat(result.getMessage()).isEqualTo(expected);
+  }
+
+  @Test
+  public void testNotFound() throws Exception {
+    String expected = "not found";
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT))
+        .willReturn(aResponse().withStatus(NOT_FOUND).withBody(expected)));
+
+    HttpResult result = httpSession.post(ENDPOINT);
+    assertThat(result.isSuccessful()).isFalse();
+    assertThat(result.getMessage()).isEqualTo(expected);
+  }
+
+  @Test
+  public void testBadResponseRetryThenOK() throws Exception {
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT)).inScenario(RETRY_AT_ERROR)
+        .whenScenarioStateIs(Scenario.STARTED).willSetStateTo(REQUEST_MADE)
+        .willReturn(aResponse().withStatus(ERROR)));
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT)).inScenario(RETRY_AT_ERROR)
+        .whenScenarioStateIs(REQUEST_MADE)
+        .willReturn(aResponse().withStatus(OK)));
+
+    assertThat(httpSession.post(ENDPOINT).isSuccessful()).isTrue();
+  }
+
+  @Test
+  public void testBadResponseRetryThenGiveUp() throws Exception {
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT))
+        .willReturn(aResponse().withStatus(ERROR).withBody(ERROR_MESSAGE)));
+
+    HttpResult result = httpSession.post(ENDPOINT);
+    assertThat(result.isSuccessful()).isFalse();
+    assertThat(result.getMessage()).isEqualTo(ERROR_MESSAGE);
+  }
+
+  @Test
+  public void testRetryAfterDelay() throws Exception {
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT)).inScenario(RETRY_AT_DELAY)
+        .whenScenarioStateIs(Scenario.STARTED).willSetStateTo(REQUEST_MADE)
+        .willReturn(aResponse().withStatus(ERROR).withFixedDelay(TIMEOUT / 2)));
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT)).inScenario(RETRY_AT_DELAY)
+        .whenScenarioStateIs(REQUEST_MADE)
+        .willReturn(aResponse().withStatus(OK)));
+
+    assertThat(httpSession.post(ENDPOINT).isSuccessful()).isTrue();
+  }
+
+  @Test
+  public void testRetryAfterTimeoutThenOK() throws Exception {
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT)).inScenario(RETRY_AT_DELAY)
+        .whenScenarioStateIs(Scenario.STARTED).willSetStateTo(REQUEST_MADE)
+        .willReturn(aResponse().withFixedDelay(TIMEOUT)));
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT)).inScenario(RETRY_AT_DELAY)
+        .whenScenarioStateIs(REQUEST_MADE).willSetStateTo(SECOND_TRY)
+        .willReturn(aResponse().withFixedDelay(TIMEOUT)));
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT)).inScenario(RETRY_AT_DELAY)
+        .whenScenarioStateIs(SECOND_TRY).willSetStateTo(THIRD_TRY)
+        .willReturn(aResponse().withFixedDelay(TIMEOUT)));
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT)).inScenario(RETRY_AT_DELAY)
+        .whenScenarioStateIs(THIRD_TRY)
+        .willReturn(aResponse().withStatus(OK)));
+
+    assertThat(httpSession.post(ENDPOINT).isSuccessful()).isTrue();
+  }
+
+  @Test(expected = SocketTimeoutException.class)
+  public void testMaxRetriesAfterTimeoutThenGiveUp() throws Exception {
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT)).inScenario(RETRY_AT_DELAY)
+        .whenScenarioStateIs(Scenario.STARTED).willSetStateTo(REQUEST_MADE)
+        .willReturn(aResponse().withFixedDelay(TIMEOUT)));
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT)).inScenario(RETRY_AT_DELAY)
+        .whenScenarioStateIs(REQUEST_MADE).willSetStateTo(SECOND_TRY)
+        .willReturn(aResponse().withFixedDelay(TIMEOUT)));
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT)).inScenario(RETRY_AT_DELAY)
+        .whenScenarioStateIs(SECOND_TRY).willSetStateTo(THIRD_TRY)
+        .willReturn(aResponse().withFixedDelay(TIMEOUT)));
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT)).inScenario(RETRY_AT_DELAY)
+        .whenScenarioStateIs(THIRD_TRY)
+        .willReturn(aResponse().withFixedDelay(TIMEOUT)));
+
+    httpSession.post(ENDPOINT);
+  }
+
+  @Test
+  public void testGiveUpAtTimeout() throws Exception {
+    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).isSuccessful()).isFalse();
+  }
+
+  @Test
+  public void testResponseWithMalformedResponse() throws Exception {
+    wireMockRule.givenThat(post(urlEqualTo(ENDPOINT))
+        .willReturn(aResponse().withFault(Fault.MALFORMED_RESPONSE_CHUNK)));
+
+    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/syncevents/ModuleTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ModuleTest.java
similarity index 74%
rename from src/test/java/com/ericsson/gerrit/plugins/syncevents/ModuleTest.java
rename to src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ModuleTest.java
index 3a0addf..be6ddc7 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/syncevents/ModuleTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ModuleTest.java
@@ -12,23 +12,24 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.syncevents;
+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 {
 
   @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);
+    RestEventForwarderModule module = new RestEventForwarderModule();
+    assertThat(module.forwardUrl(configMock)).isEqualTo(expected);
   }
 }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestEventForwarderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestEventForwarderTest.java
new file mode 100644
index 0000000..6228380
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestEventForwarderTest.java
@@ -0,0 +1,145 @@
+// 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.server.events.Event;
+import com.google.gson.GsonBuilder;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.HttpResponseHandler.HttpResult;
+
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class RestEventForwarderTest {
+  private static final int CHANGE_NUMBER = 1;
+  private static final String DELETE_OP = "delete";
+  private static final String INDEX_OP = "index";
+  private static final String PLUGIN_NAME = "multi-master";
+  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;
+
+  private RestEventForwarder eventForwarder;
+
+  @Test
+  public void testIndexChangeOK() throws Exception {
+    setUpMocksForIndex(INDEX_OP, SUCCESSFUL, EMPTY_MSG, DO_NOT_THROW_EXCEPTION);
+    assertThat(eventForwarder.indexChange(CHANGE_NUMBER)).isTrue();
+  }
+
+  @Test
+  public void testIndexChangeFailed() throws Exception {
+    setUpMocksForIndex(INDEX_OP, FAILED, ERROR_MSG, DO_NOT_THROW_EXCEPTION);
+    assertThat(eventForwarder.indexChange(CHANGE_NUMBER)).isFalse();
+  }
+
+  @Test
+  public void testIndexChangeThrowsException() throws Exception {
+    setUpMocksForIndex(INDEX_OP, FAILED, EXCEPTION_MSG, THROW_EXCEPTION);
+    assertThat(eventForwarder.indexChange(CHANGE_NUMBER)).isFalse();
+  }
+
+  @Test
+  public void testChangeDeletedFromIndexOK() throws Exception {
+    setUpMocksForIndex(DELETE_OP, SUCCESSFUL, EMPTY_MSG,
+        DO_NOT_THROW_EXCEPTION);
+    assertThat(eventForwarder.deleteChangeFromIndex(CHANGE_NUMBER)).isTrue();
+  }
+
+  @Test
+  public void testChangeDeletedFromIndexFailed() throws Exception {
+    setUpMocksForIndex(DELETE_OP, FAILED, ERROR_MSG, DO_NOT_THROW_EXCEPTION);
+    assertThat(eventForwarder.deleteChangeFromIndex(CHANGE_NUMBER)).isFalse();
+  }
+
+  @Test
+  public void testChangeDeletedFromThrowsException() throws Exception {
+    setUpMocksForIndex(DELETE_OP, FAILED, EXCEPTION_MSG, THROW_EXCEPTION);
+    assertThat(eventForwarder.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);
+      }
+    }
+    eventForwarder = new RestEventForwarder(httpSession, PLUGIN_NAME);
+  }
+
+  @Test
+  public void testEventSentOK() throws Exception {
+    Event event = setUpMocksForEvent(true, "", false);
+    assertThat(eventForwarder.send(event)).isTrue();
+  }
+
+  @Test
+  public void testEventSentFailed() throws Exception {
+    Event event = setUpMocksForEvent(false, "Error", false);
+    assertThat(eventForwarder.send(event)).isFalse();
+  }
+
+  @Test
+  public void testEventSentThrowsException() throws Exception {
+    Event event = setUpMocksForEvent(false, "Exception", true);
+    assertThat(eventForwarder.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);
+    }
+    eventForwarder = new RestEventForwarder(httpSession, PLUGIN_NAME);
+    return event;
+  }
+
+  private class EventTest extends Event {
+    public EventTest() {
+      super("test-event");
+    }
+  }
+}
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..da3d669
--- /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.EventForwarder;
+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 EventForwarder eventForwarder;
+  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, eventForwarder);
+  }
+
+  @Test
+  public void shouldIndexInRemoteOnChangeIndexedEvent() throws Exception {
+    indexEventHandler.onChangeIndexed(id.get());
+    verify(eventForwarder).indexChange(CHANGE_ID);
+  }
+
+  @Test
+  public void shouldDeleteFromIndexInRemoteOnChangeDeletedEvent()
+      throws Exception {
+    indexEventHandler.onChangeDeleted(id.get());
+    verify(eventForwarder).deleteChangeFromIndex(CHANGE_ID);
+  }
+
+  @Test
+  public void shouldNotCallRemoteWhenEventIsForwarded() throws Exception {
+    Context.setForwardedEvent(true);
+    indexEventHandler.onChangeIndexed(id.get());
+    indexEventHandler.onChangeDeleted(id.get());
+    Context.unsetForwardedEvent();
+    verifyZeroInteractions(eventForwarder);
+  }
+
+  @Test
+  public void duplicateEventOfAQueuedEventShouldGetDiscarded() {
+    Executor poolMock = mock(Executor.class);
+    indexEventHandler =
+        new IndexEventHandler(poolMock, PLUGIN_NAME, eventForwarder);
+    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/syncevents/SyncEventExecutorProviderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexExecutorProviderTest.java
similarity index 64%
copy from src/test/java/com/ericsson/gerrit/plugins/syncevents/SyncEventExecutorProviderTest.java
copy to src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexExecutorProviderTest.java
index 676e028..3db194b 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/syncevents/SyncEventExecutorProviderTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexExecutorProviderTest.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Ericsson
+// 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.
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.syncevents;
+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,35 +30,35 @@
 import org.mockito.junit.MockitoJUnitRunner;
 
 @RunWith(MockitoJUnitRunner.class)
-public class SyncEventExecutorProviderTest {
+public class IndexExecutorProviderTest {
   @Mock
   private WorkQueue.Executor executorMock;
-  private SyncEventExecutorProvider syncEventsExecutorProvider;
+  private IndexExecutorProvider indexExecutorProvider;
 
   @Before
   public void setUp() throws Exception {
+    executorMock = mock(WorkQueue.Executor.class);
     WorkQueue workQueueMock = mock(WorkQueue.class);
-    when(workQueueMock.createQueue(4, "Sync stream events [SyncEvents plugin]"))
+    when(workQueueMock.createQueue(4, "Forward-index-event"))
         .thenReturn(executorMock);
     Configuration configMock = mock(Configuration.class);
-    when(configMock.getThreadPoolSize()).thenReturn(4);
-
-    syncEventsExecutorProvider =
-        new SyncEventExecutorProvider(workQueueMock, "SyncEvents", configMock);
+    when(configMock.getIndexThreadPoolSize()).thenReturn(4);
+    indexExecutorProvider =
+        new IndexExecutorProvider(workQueueMock, configMock);
   }
 
   @Test
   public void shouldReturnExecutor() throws Exception {
-    assertThat(syncEventsExecutorProvider.get()).isEqualTo(executorMock);
+    assertThat(indexExecutorProvider.get()).isEqualTo(executorMock);
   }
 
   @Test
   public void testStop() throws Exception {
-    syncEventsExecutorProvider.start();
-    assertThat(syncEventsExecutorProvider.get()).isEqualTo(executorMock);
-    syncEventsExecutorProvider.stop();
+    indexExecutorProvider.start();
+    assertThat(indexExecutorProvider.get()).isEqualTo(executorMock);
+    indexExecutorProvider.stop();
     verify(executorMock).shutdown();
     verify(executorMock).unregisterWorkQueue();
-    assertThat(syncEventsExecutorProvider.get()).isNull();
+    assertThat(indexExecutorProvider.get()).isNull();
   }
 }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/syncevents/ContextTest.java b/src/test/java/com/ericsson/gerrit/plugins/syncevents/ContextTest.java
deleted file mode 100644
index c3dd4cd..0000000
--- a/src/test/java/com/ericsson/gerrit/plugins/syncevents/ContextTest.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.syncevents;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import org.junit.Test;
-
-import com.ericsson.gerrit.plugins.syncevents.Context;
-
-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/syncevents/HttpSessionTest.java b/src/test/java/com/ericsson/gerrit/plugins/syncevents/HttpSessionTest.java
deleted file mode 100644
index 3b8e182..0000000
--- a/src/test/java/com/ericsson/gerrit/plugins/syncevents/HttpSessionTest.java
+++ /dev/null
@@ -1,155 +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.syncevents;
-
-import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
-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.syncevents.SyncEventsResponseHandler.SyncResult;
-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.Test;
-
-public class HttpSessionTest {
-  private static final int MAX_TRIES = 3;
-  private static final int RETRY_INTERVAL = 250;
-  private static final int TIMEOUT = 500;
-  private static final int ERROR = 500;
-  private static final int OK = 204;
-  private static final int NOT_FOUND = 404;
-  private static final int UNAUTHORIZED = 401;
-
-  private static final String ENDPOINT = "/plugins/sync-events/event";
-  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 RETRY_AT_ERROR = "Retry at error";
-  private static final String RETRY_AT_DELAY = "Retry at delay";
-
-  private HttpSession httpSession;
-
-  @Rule
-  public 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(url);
-    when(cfg.getUser()).thenReturn("user");
-    when(cfg.getPassword()).thenReturn("pass");
-    when(cfg.getMaxTries()).thenReturn(MAX_TRIES);
-    when(cfg.getConnectionTimeout()).thenReturn(TIMEOUT);
-    when(cfg.getSocketTimeout()).thenReturn(TIMEOUT);
-    when(cfg.getRetryInterval()).thenReturn(RETRY_INTERVAL);
-
-    httpSession =
-        new HttpSession(new HttpClientProvider(cfg).get(), url);
-  }
-
-  @Test
-  public void testResponseOK() throws Exception {
-    stubFor(post(urlEqualTo(ENDPOINT)).willReturn(aResponse().withStatus(OK)));
-    assertThat(httpSession.post(ENDPOINT, BODY).isSuccessful()).isTrue();
-  }
-
-  @Test
-  public void testResponseOKEmptyBody() throws Exception {
-    stubFor(post(urlEqualTo(ENDPOINT)).willReturn(aResponse().withStatus(OK)));
-    assertThat(httpSession.post(ENDPOINT, "").isSuccessful()).isTrue();
-  }
-
-  @Test
-  public void testNotAuthorized() throws Exception {
-    String expected = "unauthorized";
-    stubFor(post(urlEqualTo(ENDPOINT)).willReturn(
-        aResponse().withStatus(UNAUTHORIZED).withBody(expected)));
-
-    SyncResult result = httpSession.post(ENDPOINT, BODY);
-    assertThat(result.isSuccessful()).isFalse();
-    assertThat(result.getMessage()).isEqualTo(expected);
-  }
-
-  @Test
-  public void testNotFound() throws Exception {
-    String expected = "not found";
-    stubFor(post(urlEqualTo(ENDPOINT)).willReturn(
-        aResponse().withStatus(NOT_FOUND).withBody(expected)));
-
-    SyncResult result = httpSession.post(ENDPOINT, BODY);
-    assertThat(result.isSuccessful()).isFalse();
-    assertThat(result.getMessage()).isEqualTo(expected);
-  }
-
-  @Test
-  public void testBadResponseRetryThenOK() throws Exception {
-    stubFor(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)
-        .whenScenarioStateIs(REQUEST_MADE)
-        .willReturn(aResponse().withStatus(OK)));
-
-    assertThat(httpSession.post(ENDPOINT, BODY).isSuccessful()).isTrue();
-  }
-
-  @Test
-  public void testBadResponseRetryThenGiveUp() throws Exception {
-    stubFor(post(urlEqualTo(ENDPOINT)).willReturn(
-        aResponse().withStatus(ERROR).withBody(ERROR_MESSAGE)));
-
-    assertThat(httpSession.post(ENDPOINT, BODY).isSuccessful()).isFalse();
-    assertThat(httpSession.post(ENDPOINT, BODY).getMessage())
-        .isEqualTo(ERROR_MESSAGE);
-  }
-
-  @Test
-  public void testRetryAfterDelay() throws Exception {
-    stubFor(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)
-        .whenScenarioStateIs(REQUEST_MADE)
-        .willReturn(aResponse().withStatus(OK)));
-
-    assertThat(httpSession.post(ENDPOINT, BODY).isSuccessful()).isTrue();
-  }
-
-  @Test
-  public void testGiveUpAtTimeout() throws Exception {
-    stubFor(post(urlEqualTo(ENDPOINT)).inScenario(RETRY_AT_DELAY)
-        .whenScenarioStateIs(Scenario.STARTED).willSetStateTo(REQUEST_MADE)
-        .willReturn(aResponse().withStatus(ERROR).withFixedDelay(TIMEOUT)));
-
-    assertThat(httpSession.post(ENDPOINT, BODY).isSuccessful()).isFalse();
-  }
-
-  @Test
-  public void testResponseWithMalformedResponse() throws Exception {
-    stubFor(post(urlEqualTo(ENDPOINT)).willReturn(
-        aResponse().withFault(Fault.MALFORMED_RESPONSE_CHUNK)));
-
-    assertThat(httpSession.post(ENDPOINT, BODY).isSuccessful()).isFalse();
-  }
-}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/syncevents/RestSessionTest.java b/src/test/java/com/ericsson/gerrit/plugins/syncevents/RestSessionTest.java
deleted file mode 100644
index 9385c5f..0000000
--- a/src/test/java/com/ericsson/gerrit/plugins/syncevents/RestSessionTest.java
+++ /dev/null
@@ -1,82 +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.syncevents;
-
-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.server.events.Event;
-import com.google.gson.GsonBuilder;
-
-import com.ericsson.gerrit.plugins.syncevents.SyncEventsResponseHandler.SyncResult;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.IOException;
-
-public class RestSessionTest {
-  private static final String PLUGIN_NAME = "sync-events";
-  private static final String REQUEST =
-      Joiner.on("/").join("/plugins", PLUGIN_NAME, "event");
-
-  private RestSession restSession;
-  private Event event;
-
-  @Before
-  public void setup() {
-    event = new SyncEventTest();
-  }
-
-  @Test
-  public void testEventSentOK() throws Exception {
-    setUpMocks(true, "", false);
-    assertThat(restSession.send(event)).isTrue();
-  }
-
-  @Test
-  public void testEventSentFailed() throws Exception {
-    setUpMocks(false, "Error", false);
-    assertThat(restSession.send(event)).isFalse();
-  }
-
-  @Test
-  public void testEventSentThrowsException() throws Exception {
-    setUpMocks(false, "Exception", true);
-    assertThat(restSession.send(event)).isFalse();
-  }
-
-  private void setUpMocks(boolean isOk, String msg, boolean throwException)
-      throws Exception {
-    String content = new GsonBuilder().create().toJson(event);
-    HttpSession httpSession = mock(HttpSession.class);
-    if (throwException) {
-      doThrow(new IOException()).when(httpSession).post(REQUEST, content);
-    } else {
-      SyncResult result = new SyncResult(isOk, msg);
-      when(httpSession.post(REQUEST, content)).thenReturn(result);
-    }
-    restSession = new RestSession(httpSession, PLUGIN_NAME);
-  }
-
-  class SyncEventTest extends Event {
-    public SyncEventTest() {
-      super("test-event");
-    }
-  }
-}