diff --git a/indexer-service-azure/pom.xml b/indexer-service-azure/pom.xml
index 7f76c0063895fa7be114f3281297d4190ed7c239..438bda1b1732fb17716340afa2ca0d30b06a7fcb 100644
--- a/indexer-service-azure/pom.xml
+++ b/indexer-service-azure/pom.xml
@@ -16,24 +16,38 @@
     <description>Indexer Service Azure</description>
     <packaging>jar</packaging>
 
-    <dependencies>
-        <dependency>
-            <groupId>org.opendes.core</groupId>
-            <artifactId>indexer-search-core-azure</artifactId>
-            <version>0.0.1-SNAPSHOT</version>
-            <exclusions>
-                <exclusion>
-                    <groupId>org.opendes.core</groupId>
-                    <artifactId>indexer-search-core-root</artifactId>
-                </exclusion>
-            </exclusions>
-        </dependency>
+    <properties>
+        <azure.version>2.1.7</azure.version>
+    </properties>
 
+    <dependencies>
         <dependency>
             <groupId>org.opendes.indexer</groupId>
             <artifactId>indexer-service-root</artifactId>
             <version>1.0-SNAPSHOT</version>
         </dependency>
+
+        <dependency>
+            <groupId>com.microsoft.azure</groupId>
+            <artifactId>azure-active-directory-spring-boot-starter</artifactId>
+            <version>${azure.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.microsoft.azure</groupId>
+            <artifactId>azure-cosmosdb-spring-boot-starter</artifactId>
+            <version>${azure.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.microsoft.azure</groupId >
+            <artifactId>azure-storage-spring-boot-starter</artifactId>
+            <version>${azure.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.microsoft.azure</groupId>
+            <artifactId>azure-servicebus-spring-boot-starter</artifactId>
+            <version>${azure.version}</version>
+        </dependency>
+
     </dependencies>
 
     <build>
diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/EntitlementsClientFactory.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/EntitlementsClientFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..fea892cb0cd8b19c5a147a685eae16693694fab2
--- /dev/null
+++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/EntitlementsClientFactory.java
@@ -0,0 +1,21 @@
+package org.opendes.indexer.azure.di;
+
+import org.opendes.client.api.entitlements.IEntitlementsFactory;
+import org.springframework.beans.factory.config.AbstractFactoryBean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.stereotype.Component;
+
+@Component("AzureEntitlementsClientFactory")
+@Primary
+public class EntitlementsClientFactory extends AbstractFactoryBean<IEntitlementsFactory> {
+
+    @Override
+    protected IEntitlementsFactory createInstance() throws Exception {
+        return new EntitlementsFactoryAzure();
+    }
+
+    @Override
+    public Class<?> getObjectType() {
+        return IEntitlementsFactory.class;
+    }
+}
diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/EntitlementsFactoryAzure.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/EntitlementsFactoryAzure.java
new file mode 100644
index 0000000000000000000000000000000000000000..b4dc544dfcffbae670d1f5deada5ffcfe98b1ccf
--- /dev/null
+++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/EntitlementsFactoryAzure.java
@@ -0,0 +1,12 @@
+package org.opendes.indexer.azure.di;
+
+import org.opendes.client.api.DpsHeaders;
+import org.opendes.client.api.entitlements.IEntitlementsFactory;
+import org.opendes.client.api.entitlements.IEntitlementsService;
+
+public class EntitlementsFactoryAzure implements IEntitlementsFactory {
+    @Override
+    public IEntitlementsService create(DpsHeaders headers) {
+        return new EntitlementsServiceAzure(headers);
+    }
+}
diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/EntitlementsServiceAzure.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/EntitlementsServiceAzure.java
new file mode 100644
index 0000000000000000000000000000000000000000..d805136b92d7f61bfa6a2c20058596772d576d85
--- /dev/null
+++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/EntitlementsServiceAzure.java
@@ -0,0 +1,87 @@
+package org.opendes.indexer.azure.di;
+
+import com.microsoft.azure.spring.autoconfigure.aad.UserPrincipal;
+import org.apache.http.HttpStatus;
+import org.opendes.client.api.DpsHeaders;
+import org.opendes.client.api.entitlements.EntitlementsException;
+import org.opendes.client.api.entitlements.IEntitlementsService;
+import org.opendes.client.api.entitlements.models.*;
+import org.opendes.client.httpclient.HttpResponse;
+import org.opendes.core.model.SearchServiceRole;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class EntitlementsServiceAzure implements IEntitlementsService {
+    DpsHeaders headers;
+
+    public EntitlementsServiceAzure(DpsHeaders headers){
+        this.headers = headers;
+    }
+
+    @Override
+    public MemberInfo addMember(GroupEmail groupEmail, MemberInfo memberInfo) throws EntitlementsException {
+        return null;
+    }
+
+    @Override
+    public Members getMembers(GroupEmail groupEmail, GetMembers getMembers) throws EntitlementsException {
+        return null;
+    }
+
+    @Override
+    public Groups getGroups() throws EntitlementsException {
+        final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+        final UserPrincipal current = (UserPrincipal) auth.getPrincipal();
+        String email = String.valueOf(current.getUpn());
+
+        List<GroupInfo> giList = new ArrayList();
+        Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
+        for(GrantedAuthority authority : authorities)
+        {
+            GroupInfo gi = new GroupInfo();
+            String role = authority.getAuthority();
+            if (role.startsWith(SearchServiceRole.PREFIX)){
+                role = role.substring(SearchServiceRole.PREFIX.length());
+            }
+            gi.setName(role);
+            gi.setEmail(email);
+            giList.add(gi);
+        }
+        if (giList.size() > 0)
+        {
+            Groups groups = new Groups();
+            groups.setGroups(giList);
+            groups.setDesId(email);
+            return groups;
+        }
+
+        HttpResponse response = new HttpResponse();
+        response.setResponseCode(HttpStatus.SC_INTERNAL_SERVER_ERROR);
+        throw new EntitlementsException("no authorities found", response);
+    }
+
+    @Override
+    public GroupInfo createGroup(CreateGroup createGroup) throws EntitlementsException {
+        return null;
+    }
+
+    @Override
+    public void deleteMember(String s, String s1) throws EntitlementsException {
+
+    }
+
+    @Override
+    public Groups authorizeAny(String... strings) throws EntitlementsException {
+        return null;
+    }
+
+    @Override
+    public void authenticate() throws EntitlementsException {
+
+    }
+}
diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/TenantFactoryImpl.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/TenantFactoryImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..ef4233b70eee64d26893736b28423966e11d619b
--- /dev/null
+++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/TenantFactoryImpl.java
@@ -0,0 +1,45 @@
+package org.opendes.indexer.azure.di;
+
+import org.opendes.client.cache.ICache;
+import org.opendes.client.multitenancy.ITenantFactory;
+import org.opendes.client.multitenancy.TenantInfo;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@Component
+public class TenantFactoryImpl implements ITenantFactory {
+    public static final String DefaultTenantName = "common";
+    private List<TenantInfo> tenants;
+
+    public TenantFactoryImpl()
+    {
+        TenantInfo ti = new TenantInfo();
+        ti.setName(DefaultTenantName);
+        this.tenants = new ArrayList<>();
+        this.tenants.add(ti);
+    }
+
+    public boolean exists(String tenantName)
+    {
+        return true;
+    }
+
+    public TenantInfo getTenantInfo(String tenantName) {
+        // we are not checking tenantName yet, we have only 1 tenant
+        return this.tenants.get(0);
+    }
+
+    public Collection<TenantInfo> listTenantInfo() {
+        return this.tenants;
+    }
+
+    public <V> ICache<String, V> createCache(String tenantName, String host, int port, int expireTimeSeconds, Class<V> classOfV)
+    {
+        return null;
+    }
+
+    public void flushCache() {}
+}
diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/kms/KmsClientImpl.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/kms/KmsClientImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..8e1b169e602d7595cad7f9e5b7bda4a411381e22
--- /dev/null
+++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/kms/KmsClientImpl.java
@@ -0,0 +1,17 @@
+package org.opendes.indexer.azure.kms;
+
+import org.opendes.core.kms.IKmsClient;
+
+import java.io.IOException;
+
+public class KmsClientImpl implements IKmsClient {
+    @Override
+    public String encryptString(String textToBeEncrypted) throws IOException {
+        return textToBeEncrypted;
+    }
+
+    @Override
+    public String decryptString(String textToBeDecrypted) throws IOException {
+        return textToBeDecrypted;
+    }
+}
diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/model/ISchemaRepository.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/model/ISchemaRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..5f2d7dd978c701e91c59a4ede9818c7f10195c0c
--- /dev/null
+++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/model/ISchemaRepository.java
@@ -0,0 +1,30 @@
+
+
+// Copyright 2017-2019, Schlumberger
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.opendes.indexer.azure.model;
+
+public interface ISchemaRepository {
+    String SCHEMA_KIND = "IndexerSchema";
+
+    String SCHEMA = "schema";
+    String USER = "user";
+    String EXTENSION = "extension";
+
+    void add(Schema schema, String user);
+
+    Schema get(String kind);
+}
+
diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/model/Schema.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/model/Schema.java
new file mode 100644
index 0000000000000000000000000000000000000000..14b1cf950ce8207aeb772c0dbf23f02836fae203
--- /dev/null
+++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/model/Schema.java
@@ -0,0 +1,46 @@
+// Copyright 2017-2019, Schlumberger
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.opendes.indexer.azure.model;
+
+        import java.util.Map;
+
+        import javax.validation.Valid;
+        import javax.validation.constraints.NotNull;
+
+        import lombok.AllArgsConstructor;
+        import lombok.Data;
+        import lombok.NoArgsConstructor;
+
+        import io.swagger.annotations.ApiModelProperty;
+        import org.opendes.indexer.SwaggerDoc;
+        import org.opendes.core.validation.ValidKind;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Schema {
+
+    @ValidKind
+    @NotNull
+    @ApiModelProperty(value = SwaggerDoc.SCHEMA_REQUEST_KIND,
+            required = true,
+            example = SwaggerDoc.RECORD_KIND_EXAMPLE)
+    private String kind;
+
+    @Valid
+    private SchemaItem[] schema;
+
+    private Map<String, Object> ext;
+}
\ No newline at end of file
diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/model/SchemaItem.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/model/SchemaItem.java
new file mode 100644
index 0000000000000000000000000000000000000000..d4fe01a153d8b38733207a639a4d2bea0754d503
--- /dev/null
+++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/model/SchemaItem.java
@@ -0,0 +1,41 @@
+// Copyright 2017-2019, Schlumberger
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.opendes.indexer.azure.model;
+
+import java.util.Map;
+
+import javax.validation.constraints.NotEmpty;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class SchemaItem {
+
+    @NotEmpty
+    private String path;
+
+    @NotEmpty
+    private String kind;
+
+    @JsonInclude(value = Include.NON_NULL)
+    private Map<String, Object> ext;
+}
diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/persistence/ElasticRepositoryCosmosDB.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/persistence/ElasticRepositoryCosmosDB.java
new file mode 100644
index 0000000000000000000000000000000000000000..b5796c629c3eef1dbbff4ece7e914ebe5520eaf3
--- /dev/null
+++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/persistence/ElasticRepositoryCosmosDB.java
@@ -0,0 +1,12 @@
+package org.opendes.indexer.azure.persistence;
+
+import org.opendes.client.multitenancy.TenantInfo;
+import org.opendes.core.model.ClusterSettings;
+import org.opendes.core.persistence.ElasticRepository;
+
+public class ElasticRepositoryCosmosDB implements ElasticRepository {
+    @Override
+    public ClusterSettings getElasticClusterSettings(TenantInfo tenantInfo) {
+        return null;
+    }
+}
diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/persistence/SchemaRepositoryImpl.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/persistence/SchemaRepositoryImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..a0a6445e608ff097a0de1dc000b8470d2ba910b1
--- /dev/null
+++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/persistence/SchemaRepositoryImpl.java
@@ -0,0 +1,63 @@
+package org.opendes.indexer.azure.persistence;
+
+import com.microsoft.azure.spring.data.cosmosdb.core.mapping.Document;
+import com.microsoft.azure.spring.data.cosmosdb.core.mapping.PartitionKey;
+import com.microsoft.azure.spring.data.cosmosdb.repository.DocumentDbRepository;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.opendes.indexer.azure.model.Schema;
+import org.opendes.indexer.azure.model.SchemaItem;
+import org.opendes.indexer.azure.model.ISchemaRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.annotation.Id;
+import org.springframework.stereotype.Repository;
+
+import java.util.Map;
+import java.util.Optional;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Document(collection = "StorageSchema") //collection name
+class SchemaDoc {
+
+    @PartitionKey
+    @Id
+    private String kind;
+    private Map<String,Object> extension;
+    private String user;
+    private SchemaItem[] schemaItems;
+}
+
+interface CosmosDB extends DocumentDbRepository<SchemaDoc, String> {}
+
+@Repository
+public class SchemaRepositoryImpl implements ISchemaRepository {
+
+    @Autowired
+    private CosmosDB db;
+
+    @Override
+    public void add(Schema schema, String user) {
+        SchemaDoc sd = new SchemaDoc();
+        sd.setKind(schema.getKind());
+        sd.setExtension(schema.getExt());
+        sd.setUser(user);
+        sd.setSchemaItems(schema.getSchema());
+        db.save(sd);
+    }
+
+    @Override
+    public Schema get(String kind) {
+        Optional<SchemaDoc> sd = db.findById(kind);
+        if (!sd.isPresent())
+            return null;
+
+        Schema newSchema = new Schema();
+        newSchema.setKind(kind);
+        newSchema.setSchema(sd.get().getSchemaItems());
+        newSchema.setExt(sd.get().getExtension());
+        return newSchema;
+    }
+}
diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/util/RequestInfoImpl.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/util/RequestInfoImpl.java
index eccd28d599acbcf4d2b697a25d290a5e36d1532b..a1210a82345698a0ac231913a467adab195ed8fe 100644
--- a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/util/RequestInfoImpl.java
+++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/util/RequestInfoImpl.java
@@ -1,11 +1,13 @@
 package org.opendes.indexer.azure.util;
 
 import com.google.common.base.Strings;
-import com.slb.core.model.AppEngineHeaders;
-import com.slb.core.model.DeploymentEnvironment;
-import com.slb.core.util.HeadersInfo;
 import org.apache.http.HttpStatus;
 import org.opendes.client.api.DpsHeaders;
+import org.opendes.client.multitenancy.TenantInfo;
+import org.opendes.core.model.DeploymentEnvironment;
+import org.opendes.core.util.AppException;
+import org.opendes.core.util.Config;
+import org.opendes.core.util.HeadersInfo;
 import org.opendes.indexer.util.IRequestInfo;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -22,9 +24,11 @@ public class RequestInfoImpl implements IRequestInfo {
 
     private static final String expectedCronHeaderValue = "true";
 
+    @Autowired
+    private TenantInfo tenantInfo;
+
     @Override
     public DpsHeaders getHeaders() {
-
         return this.headersInfo.getHeaders();
     }
 
@@ -51,18 +55,10 @@ public class RequestInfoImpl implements IRequestInfo {
     }
 
     @Override
-    public boolean isCronRequest() {
-        String appEngineCronHeader = this.headersInfo.getHeadersMap().getOrDefault(AppEngineHeaders.CRON_SERVICE, null);
-        return expectedCronHeaderValue.equalsIgnoreCase(appEngineCronHeader);
-    }
+    public boolean isCronRequest() { return false; }
 
     @Override
-    public boolean isTaskQueueRequest() {
-        if (!this.headersInfo.getHeadersMap().containsKey(AppEngineHeaders.TASK_QUEUE_NAME)) return false;
-
-        String queueId = this.headersInfo.getHeadersMap().get(AppEngineHeaders.TASK_QUEUE_NAME);
-        return queueId.endsWith(Constants.INDEXER_QUEUE_IDENTIFIER);
-    }
+    public boolean isTaskQueueRequest() { return false; }
 
     private String checkOrGetAuthorizationHeader() {
         if (Config.getDeploymentEnvironment() == DeploymentEnvironment.LOCAL) {
@@ -72,7 +68,8 @@ public class RequestInfoImpl implements IRequestInfo {
             }
             return authHeader;
         } else {
-            return "Bearer " + this.serviceAccountJwtClient.getIdToken();
+            return "Bearer " + this.serviceAccountJwtClient.getIdToken(tenantInfo.getName());
         }
     }
+
 }
diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/util/ServiceAccountJwtClientImpl.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/util/ServiceAccountJwtClientImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..609a5725594a463613222b4037ef62fff1915a12
--- /dev/null
+++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/util/ServiceAccountJwtClientImpl.java
@@ -0,0 +1,12 @@
+package org.opendes.indexer.azure.util;
+
+import org.opendes.core.util.IServiceAccountJwtClient;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ServiceAccountJwtClientImpl implements IServiceAccountJwtClient {
+    @Override
+    public String getIdToken(String tenantName){
+        return "common";
+    }
+}
diff --git a/indexer-service-gcp/pom.xml b/indexer-service-gcp/pom.xml
index b95598f18a3be4fd84ae9f39a6ed6d570f2854f1..87f79f80e85cf9ad3475252a1c2988121ab6f0f3 100644
--- a/indexer-service-gcp/pom.xml
+++ b/indexer-service-gcp/pom.xml
@@ -18,49 +18,93 @@
 
     <dependencies>
         <dependency>
-            <groupId>org.opendes.core</groupId>
-            <artifactId>indexer-search-core-gcp</artifactId>
-            <version>1.1-SNAPSHOT</version>
+            <groupId>org.opendes.indexer</groupId>
+            <artifactId>indexer-service-root</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.os</groupId>
+            <artifactId>client-lib-gcp</artifactId>
+            <version>0.0.8</version>
             <exclusions>
                 <exclusion>
-                    <groupId>org.opendes.core</groupId>
-                    <artifactId>indexer-search-core-root</artifactId>
+                    <groupId>org.os</groupId>
+                    <artifactId>client-lib</artifactId>
                 </exclusion>
             </exclusions>
         </dependency>
 
         <dependency>
-            <groupId>org.opendes.indexer</groupId>
-            <artifactId>indexer-service-root</artifactId>
-            <version>1.0-SNAPSHOT</version>
+            <groupId>com.google.cloud</groupId>
+            <artifactId>google-cloud-datastore</artifactId>
+            <version>1.72.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.cloud</groupId>
+            <artifactId>google-cloud-logging</artifactId>
+            <version>1.72.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.apis</groupId>
+            <artifactId>google-api-services-storage</artifactId>
+            <version>v1-rev150-1.25.0</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.google.guava</groupId>
+                    <artifactId>guava-jdk5</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.google.apis</groupId>
+            <artifactId>google-api-services-cloudkms</artifactId>
+            <version>v1-rev81-1.25.0</version>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/com.google.cloud/google-cloud-pubsub -->
+        <dependency>
+            <groupId>com.google.cloud</groupId>
+            <artifactId>google-cloud-pubsub</artifactId>
+            <version>1.60.0</version>
         </dependency>
-
-<!--        <dependency>-->
-<!--            <groupId>com.google.cloud</groupId>-->
-<!--            <artifactId>google-cloud-datastore</artifactId>-->
-<!--            <version>1.72.0</version>-->
-<!--        </dependency>-->
-<!--        <dependency>-->
-<!--            <groupId>com.google.cloud</groupId>-->
-<!--            <artifactId>google-cloud-logging</artifactId>-->
-<!--            <version>1.72.0</version>-->
-<!--        </dependency>-->
         <dependency>
             <groupId>com.google.appengine.tools</groupId>
             <artifactId>appengine-gcs-client</artifactId>
             <version>0.8</version>
         </dependency>
-
-        <!--        <dependency>-->
-<!--            <groupId>com.google.apis</groupId>-->
-<!--            <artifactId>google-api-services-storage</artifactId>-->
-<!--            <version>v1-rev150-1.25.0</version>-->
-<!--            <exclusions>-->
-<!--                <exclusion>-->
-<!--                    <groupId>com.google.guava</groupId>-->
-<!--                    <artifactId>guava-jdk5</artifactId>-->
-<!--                </exclusion>-->
-<!--            </exclusions>-->
+<!--        <dependency>-->
+<!--            <groupId>org.mockito</groupId>-->
+<!--            <artifactId>mockito-core</artifactId>-->
+<!--            <scope>test</scope>-->
+<!--        </dependency>-->
+<!--        <dependency>-->
+<!--            <groupId>junit</groupId>-->
+<!--            <artifactId>junit</artifactId>-->
+<!--            <scope>test</scope>-->
+<!--        </dependency>-->
+<!--        <dependency>-->
+<!--            <groupId>org.powermock</groupId>-->
+<!--            <artifactId>powermock-core</artifactId>-->
+<!--            <version>2.0.2</version>-->
+<!--            <scope>test</scope>-->
+<!--        </dependency>-->
+<!--        <dependency>-->
+<!--            <groupId>org.powermock</groupId>-->
+<!--            <artifactId>powermock-api-mockito2</artifactId>-->
+<!--            <version>2.0.2</version>-->
+<!--            <scope>test</scope>-->
+<!--        </dependency>-->
+<!--        <dependency>-->
+<!--            <groupId>org.springframework</groupId>-->
+<!--            <artifactId>spring-test</artifactId>-->
+<!--            <version>5.1.9.RELEASE</version>-->
+<!--            <scope>test</scope>-->
+<!--        </dependency>-->
+<!--        <dependency>-->
+<!--            <groupId>org.springframework</groupId>-->
+<!--            <artifactId>spring-test</artifactId>-->
+<!--            <version>5.1.9.RELEASE</version>-->
+<!--            <scope>test</scope>-->
 <!--        </dependency>-->
     </dependencies>
 
diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/IndexerGcpApplication.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/IndexerGcpApplication.java
index 07ae5c446b998a9dc8b3e506dc5fdd487afefcd6..402e5c8a2a836b1b97b14a9752b316eb5955802a 100644
--- a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/IndexerGcpApplication.java
+++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/IndexerGcpApplication.java
@@ -10,4 +10,4 @@ public class IndexerGcpApplication {
         SpringApplication.run(IndexerGcpApplication.class, args);
     }
 
-}
\ No newline at end of file
+}
diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/cache/DatastoreCredentialCache.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/cache/DatastoreCredentialCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..49f1b4399493d547d171ad6967c7e9d5c6aad354
--- /dev/null
+++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/cache/DatastoreCredentialCache.java
@@ -0,0 +1,27 @@
+// Copyright 2017-2019, Schlumberger
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.opendes.indexer.gcp.cache;
+
+import com.google.auth.oauth2.AccessToken;
+import org.opendes.client.cache.RedisCache;
+import org.springframework.beans.factory.annotation.Value;
+
+public class DatastoreCredentialCache extends RedisCache<String, AccessToken> {
+
+	// Datastore credentials are only valid for 1hr, release the key 2 minutes before the expiration
+	public DatastoreCredentialCache(@Value("${REDIS_SEARCH_HOST}") final String REDIS_SEARCH_HOST, @Value("${REDIS_SEARCH_PORT}") final String REDIS_SEARCH_PORT) {
+		super(REDIS_SEARCH_HOST, Integer.parseInt(REDIS_SEARCH_PORT), 58 * 60, String.class, AccessToken.class);
+	}
+}
\ No newline at end of file
diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/kms/KmsClient.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/kms/KmsClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..a57b236760cbc8bff2c9f6cb9a46e431185312a1
--- /dev/null
+++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/kms/KmsClient.java
@@ -0,0 +1,96 @@
+// Copyright 2017-2019, Schlumberger
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.opendes.indexer.gcp.kms;
+
+import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
+import com.google.api.client.http.HttpTransport;
+import com.google.api.client.http.javanet.NetHttpTransport;
+import com.google.api.client.json.JsonFactory;
+import com.google.api.client.json.jackson2.JacksonFactory;
+import com.google.api.services.cloudkms.v1.CloudKMS;
+import com.google.api.services.cloudkms.v1.CloudKMSScopes;
+import com.google.api.services.cloudkms.v1.model.DecryptRequest;
+import com.google.api.services.cloudkms.v1.model.DecryptResponse;
+import com.google.api.services.cloudkms.v1.model.EncryptRequest;
+import com.google.api.services.cloudkms.v1.model.EncryptResponse;
+import org.opendes.core.kms.IKmsClient;
+import org.opendes.core.util.Config;
+import org.opendes.core.util.Preconditions;
+import org.springframework.beans.factory.annotation.Value;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+public class KmsClient implements IKmsClient {
+
+    @Value("${KMS_KEY}")
+    private String KMS_KEY;
+
+    @Value("${KEY_RING}")
+    private String KEY_RING;
+
+    private static final String KEY_NAME = "projects/%s/locations/global/keyRings/%s/cryptoKeys/%s";
+
+    /**
+     * Encrypts the given plaintext using the specified crypto key.
+     * Google KMS automatically uses the new primary key version to encrypt data, so this could be directly used for key rotation
+     */
+    public String encryptString(String textToBeEncrypted) throws IOException {
+        Preconditions.checkNotNullOrEmpty(textToBeEncrypted, "textToBeEncrypted cannot be null");
+
+        byte[] plaintext = textToBeEncrypted.getBytes(StandardCharsets.UTF_8);
+        String resourceName = String.format(KEY_NAME, Config.getGoogleCloudProjectId(), KEY_RING, KMS_KEY);
+        CloudKMS kms = createAuthorizedClient();
+        EncryptRequest request = new EncryptRequest().encodePlaintext(plaintext);
+        EncryptResponse response = kms.projects().locations().keyRings().cryptoKeys()
+                .encrypt(resourceName, request)
+                .execute();
+        return response.getCiphertext();
+    }
+
+    /**
+     * Decrypts the provided ciphertext with the specified crypto key.
+     * Google KMS automatically uses the correct key version to decrypt data, as long as the key version is not disabled
+     */
+    public String decryptString(String textToBeDecrypted) throws IOException {
+        Preconditions.checkNotNullOrEmpty(textToBeDecrypted, "textToBeDecrypted cannot be null");
+
+        CloudKMS kms = createAuthorizedClient();
+        String cryptoKeyName = String.format(KEY_NAME, Config.getGoogleCloudProjectId(), KEY_RING, KMS_KEY);
+        DecryptRequest request = new DecryptRequest().setCiphertext(textToBeDecrypted);
+        DecryptResponse response = kms.projects().locations().keyRings().cryptoKeys()
+                .decrypt(cryptoKeyName, request)
+                .execute();
+        return new String(response.decodePlaintext(), StandardCharsets.UTF_8).trim();
+    }
+
+    /**
+     * Creates an authorized CloudKMS client service using Application Default Credentials.
+     *
+     * @return an authorized CloudKMS client
+     * @throws IOException if there's an error getting the default credentials.
+     */
+    private CloudKMS createAuthorizedClient() throws IOException {
+        HttpTransport transport = new NetHttpTransport();
+        JsonFactory jsonFactory = new JacksonFactory();
+        GoogleCredential credential = GoogleCredential.getApplicationDefault();
+        if (credential.createScopedRequired()) {
+            credential = credential.createScoped(CloudKMSScopes.all());
+        }
+        return new CloudKMS.Builder(transport, jsonFactory, credential)
+                .setApplicationName("CloudKMS snippets")
+                .build();
+    }
+}
\ No newline at end of file
diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/model/AppEngineHeaders.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/model/AppEngineHeaders.java
new file mode 100644
index 0000000000000000000000000000000000000000..9c14a63a5fe1118bf7db9b18dba5dbb5e4e29d45
--- /dev/null
+++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/model/AppEngineHeaders.java
@@ -0,0 +1,28 @@
+// Copyright 2017-2019, Schlumberger
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.opendes.indexer.gcp.model;
+
+public class AppEngineHeaders {
+    public static final String DATA_GROUPS = "X-Data-Groups";
+    public static final String TASK_QUEUE_RETRY_COUNT = "X-AppEngine-TaskExecutionCount";
+    public static final String TASK_QUEUE_NAME = "X-AppEngine-QueueName";
+    public static final String CITY_LAT_LONG = "X-AppEngine-CityLatLong";
+    public static final String COUNTRY = "X-AppEngine-Country";
+    public static final String REGION = "X-AppEngine-Region";
+    public static final String CITY = "X-AppEngine-City";
+    public static final String CLOUD_TRACE_CONTEXT = "X-Cloud-Trace-Context";
+    public static final String TRACE_ID = "X-Trace-Id";
+    public static final String CRON_SERVICE = "X-AppEngine-Cron";
+}
\ No newline at end of file
diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/persistence/DatastoreCredential.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/persistence/DatastoreCredential.java
new file mode 100644
index 0000000000000000000000000000000000000000..4a8d7284a7f9f461bd508e921aaec9c8cef53ae2
--- /dev/null
+++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/persistence/DatastoreCredential.java
@@ -0,0 +1,109 @@
+// Copyright 2017-2019, Schlumberger
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.opendes.indexer.gcp.persistence;
+
+import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
+import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
+import com.google.api.client.json.JsonFactory;
+import com.google.api.client.json.jackson2.JacksonFactory;
+import com.google.api.services.iam.v1.Iam;
+import com.google.api.services.iam.v1.Iam.Projects.ServiceAccounts.SignJwt;
+import com.google.api.services.iam.v1.model.SignJwtRequest;
+import com.google.api.services.iam.v1.model.SignJwtResponse;
+import com.google.auth.oauth2.AccessToken;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.gson.JsonObject;
+import org.apache.commons.lang3.time.DateUtils;
+import org.opendes.client.cryptographic.Crc32c;
+import org.opendes.client.multitenancy.TenantInfo;
+import org.opendes.indexer.gcp.cache.DatastoreCredentialCache;
+
+import java.util.Date;
+
+public class DatastoreCredential extends GoogleCredentials {
+
+	private static final long serialVersionUID = 8344377091688956815L;
+	private static final JsonFactory JSON_FACTORY = new JacksonFactory();
+	private Iam iam;
+
+	private final TenantInfo tenant;
+	private final DatastoreCredentialCache cache;
+
+	protected DatastoreCredential(TenantInfo tenant, DatastoreCredentialCache cache) {
+		this.tenant = tenant;
+		this.cache = cache;
+	}
+
+	@Override
+	public AccessToken refreshAccessToken() {
+
+		String cacheKey = this.getCacheKey();
+
+		AccessToken accessToken = this.cache.get(cacheKey);
+
+		if (accessToken != null) {
+			return accessToken;
+		}
+
+		try {
+			SignJwtRequest signJwtRequest = new SignJwtRequest();
+			signJwtRequest.setPayload(this.getPayload());
+
+			String serviceAccountName = String.format("projects/-/serviceAccounts/%s", this.tenant.getServiceAccount());
+
+			SignJwt signJwt = this.getIam().projects().serviceAccounts().signJwt(serviceAccountName, signJwtRequest);
+
+			SignJwtResponse signJwtResponse = signJwt.execute();
+			String signedJwt = signJwtResponse.getSignedJwt();
+
+			accessToken = new AccessToken(signedJwt, DateUtils.addSeconds(new Date(), 3600));
+
+			this.cache.put(cacheKey, accessToken);
+
+			return accessToken;
+		} catch (Exception e) {
+			throw new RuntimeException("Error creating datastore credential", e);
+		}
+	}
+
+	private String getPayload() {
+		JsonObject payload = new JsonObject();
+		payload.addProperty("iss", this.tenant.getServiceAccount());
+		payload.addProperty("sub", this.tenant.getServiceAccount());
+		payload.addProperty("aud", "https://datastore.googleapis.com/google.datastore.v1.Datastore");
+		payload.addProperty("iat", System.currentTimeMillis() / 1000);
+
+		return payload.toString();
+	}
+
+	protected void setIam(Iam iam) {
+		this.iam = iam;
+	}
+
+	private Iam getIam() throws Exception {
+		if (this.iam == null) {
+
+			Iam.Builder builder = new Iam.Builder(GoogleNetHttpTransport.newTrustedTransport(), JSON_FACTORY,
+					GoogleCredential.getApplicationDefault()).setApplicationName("Search Service");
+
+			this.iam = builder.build();
+		}
+		return this.iam;
+	}
+
+	private String getCacheKey() {
+		return Crc32c.hashToBase64EncodedString(String.format("datastoreCredential:%s", this.tenant.getName()));
+	}
+}
\ No newline at end of file
diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/persistence/DatastoreFactory.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/persistence/DatastoreFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..5d55a6863bb48fd1f857d2f4b9c4c89741fe54c7
--- /dev/null
+++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/persistence/DatastoreFactory.java
@@ -0,0 +1,65 @@
+// Copyright 2017-2019, Schlumberger
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.opendes.indexer.gcp.persistence;
+
+import com.google.api.gax.retrying.RetrySettings;
+import com.google.cloud.TransportOptions;
+import com.google.cloud.datastore.Datastore;
+import com.google.cloud.datastore.DatastoreOptions;
+import com.google.cloud.http.HttpTransportOptions;
+import org.opendes.client.multitenancy.TenantInfo;
+import org.opendes.indexer.gcp.cache.DatastoreCredentialCache;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.threeten.bp.Duration;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DatastoreFactory {
+
+    @Autowired
+    private DatastoreCredentialCache cache;
+
+    private static Map<String, Datastore> DATASTORE_CLIENTS = new HashMap<>();
+
+    private static final RetrySettings RETRY_SETTINGS = RetrySettings.newBuilder()
+            .setMaxAttempts(6)
+            .setInitialRetryDelay(Duration.ofSeconds(10))
+            .setMaxRetryDelay(Duration.ofSeconds(32))
+            .setRetryDelayMultiplier(2.0)
+            .setTotalTimeout(Duration.ofSeconds(50))
+            .setInitialRpcTimeout(Duration.ofSeconds(50))
+            .setRpcTimeoutMultiplier(1.0)
+            .setMaxRpcTimeout(Duration.ofSeconds(50))
+            .build();
+
+    private static final TransportOptions TRANSPORT_OPTIONS = HttpTransportOptions.newBuilder()
+            .setReadTimeout(30000)
+            .build();
+
+    public Datastore getDatastoreInstance(TenantInfo tenantInfo) {
+        if (DATASTORE_CLIENTS.get(tenantInfo.getName()) == null) {
+            Datastore googleDatastore = DatastoreOptions.newBuilder()
+                    .setCredentials(new DatastoreCredential(tenantInfo, this.cache))
+                    .setRetrySettings(RETRY_SETTINGS)
+                    .setTransportOptions(TRANSPORT_OPTIONS)
+                    .setNamespace(tenantInfo.getName())
+                    .setProjectId(tenantInfo.getProjectId())
+                    .build().getService();
+            DATASTORE_CLIENTS.put(tenantInfo.getName(), googleDatastore);
+        }
+        return DATASTORE_CLIENTS.get(tenantInfo.getName());
+    }
+}
diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/persistence/ElasticRepositoryDatastore.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/persistence/ElasticRepositoryDatastore.java
new file mode 100644
index 0000000000000000000000000000000000000000..fed47389b5613a2067d3fb9d5d243f082239be16
--- /dev/null
+++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/persistence/ElasticRepositoryDatastore.java
@@ -0,0 +1,81 @@
+// Copyright 2017-2019, Schlumberger
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.opendes.indexer.gcp.persistence;
+
+import com.google.api.client.googleapis.json.GoogleJsonResponseException;
+import com.google.cloud.datastore.Datastore;
+import com.google.cloud.datastore.Entity;
+import com.google.cloud.datastore.Key;
+import org.apache.http.HttpStatus;
+import org.opendes.client.multitenancy.TenantInfo;
+import org.opendes.core.model.ClusterSettings;
+import org.opendes.core.persistence.ElasticRepository;
+import org.opendes.core.util.AppException;
+import org.opendes.core.util.Config;
+import org.opendes.core.util.Preconditions;
+import org.opendes.indexer.gcp.kms.KmsClient;
+import org.springframework.beans.factory.annotation.Autowired;
+
+
+public class ElasticRepositoryDatastore implements ElasticRepository {
+
+    static final String HOST = "host";
+    static final String PORT = "port";
+    static final String XPACK_RESTCLIENT_CONFIGURATION = "configuration";
+
+    @Autowired
+    private KmsClient kmsClient;
+    @Autowired
+    private DatastoreFactory datastoreFactory;
+
+    @Override
+    public ClusterSettings getElasticClusterSettings(TenantInfo tenantInfo) {
+
+        Datastore googleDatastore = this.datastoreFactory.getDatastoreInstance(tenantInfo);
+        Key key = googleDatastore.newKeyFactory().setKind(Config.getElasticCredentialsDatastoreKind()).newKey(Config.getElasticCredentialsDatastoreId());
+        Entity datastoreEntity = googleDatastore.get(key);
+
+        if (datastoreEntity == null) {
+            throw new AppException(HttpStatus.SC_NOT_FOUND, "Cluster setting not found", "The requested cluster setting was not found in datastore.", String.format("Cluster setting with key: '%s' does not exist in datastore.", key.getName()));
+        }
+
+        String encryptedHost = null;
+        String encryptedPort = null;
+        String encryptedConfiguration = null;
+
+        try {
+            encryptedHost = datastoreEntity.getString(HOST);
+            encryptedPort = datastoreEntity.getString(PORT);
+            encryptedConfiguration = datastoreEntity.getString(XPACK_RESTCLIENT_CONFIGURATION);
+
+            String host = this.kmsClient.decryptString(encryptedHost);
+            String portString = this.kmsClient.decryptString(encryptedPort);
+            String usernameAndPassword = this.kmsClient.decryptString(encryptedConfiguration);
+
+            Preconditions.checkNotNullOrEmpty(host, "host cannot be null");
+            Preconditions.checkNotNullOrEmpty(portString, "port cannot be null");
+            Preconditions.checkNotNullOrEmpty(usernameAndPassword, "configuration cannot be null");
+
+            int port = Integer.parseInt(portString);
+
+            return new ClusterSettings(host, port, usernameAndPassword);
+        } catch (GoogleJsonResponseException e) {
+            String debuggingInfo = String.format("Host: %s | port: %s | configuration: %s", encryptedHost, encryptedPort, encryptedConfiguration);
+            throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Cluster setting decryption error", "An error has occurred decrypting cluster settings.", debuggingInfo, e);
+        } catch (Exception e) {
+            throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Cluster setting fetch error", "An error has occurred fetching cluster settings from the datastore.", e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/publish/PublisherImpl.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/publish/PublisherImpl.java
index c40adc97f93ec4dee716febc343f659cd72efa7c..67198fb068c0affdedeef181cec2c5a63cb18497 100644
--- a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/publish/PublisherImpl.java
+++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/publish/PublisherImpl.java
@@ -23,16 +23,16 @@ import com.google.gson.reflect.TypeToken;
 import com.google.protobuf.ByteString;
 import com.google.pubsub.v1.ProjectTopicName;
 import com.google.pubsub.v1.PubsubMessage;
-import com.slb.core.model.AppEngineHeaders;
-import com.slb.core.model.DeploymentEnvironment;
-import com.slb.core.util.AppException;
-import com.slb.core.util.Config;
 import org.apache.http.HttpStatus;
 import org.elasticsearch.common.Strings;
 import org.opendes.client.api.DpsHeaders;
 import org.opendes.client.gcp.PubSub.PubSubExtensions;
 import org.opendes.client.multitenancy.ITenantFactory;
 import org.opendes.client.multitenancy.TenantInfo;
+import org.opendes.core.model.DeploymentEnvironment;
+import org.opendes.core.util.AppException;
+import org.opendes.core.util.Config;
+import org.opendes.indexer.gcp.model.AppEngineHeaders;
 import org.opendes.indexer.model.RecordStatus;
 import org.opendes.indexer.publish.IPublisher;
 import org.opendes.indexer.util.JobStatus;
@@ -59,9 +59,9 @@ public class PublisherImpl implements IPublisher {
     private PubSubExtensions pubSubExtensions;
 
     @Override
-    public PubsubMessage publishStatusChangedTagsToTopic(DpsHeaders headers, JobStatus indexerBatchStatus) throws Exception {
+    public void publishStatusChangedTagsToTopic(DpsHeaders headers, JobStatus indexerBatchStatus) throws Exception {
 
-        if (Config.getDeploymentEnvironment() == DeploymentEnvironment.LOCAL) return null;
+        if (Config.getDeploymentEnvironment() == DeploymentEnvironment.LOCAL);
 
         String tenant = headers.getPartitionId();
         if(Strings.isNullOrEmpty(tenant))
@@ -74,7 +74,7 @@ public class PublisherImpl implements IPublisher {
         PubsubMessage pubsubMessage = getPubsubMessage(headers, indexerBatchStatus);
 
         pubSubExtensions.publishAndCreateTopicIfNotExist(publisher, pubsubMessage);
-        return pubsubMessage;
+
     }
 
     private static final RetrySettings RETRY_SETTINGS = RetrySettings.newBuilder()
@@ -99,13 +99,13 @@ public class PublisherImpl implements IPublisher {
         String tenant = headers.getPartitionId();
         //This code it to provide backward compatibility to slb-account-id
         if(!Strings.isNullOrEmpty(tenant)) {
-            builder.putAttributes(DpsHeaders.DATA_PARITION_ID, headers.getPartitionId());
+            builder.putAttributes(DpsHeaders.DATA_PARTITION_ID, headers.getPartitionId());
         } else {
             builder.putAttributes(DpsHeaders.ACCOUNT_ID, headers.getAccountId());
         }
 
         builder.putAttributes(DpsHeaders.CORRELATION_ID, headers.getCorrelationId());
-        builder.putAttributes(AppEngineHeaders.CLOUD_TRACE_CONTEXT, headers.getHeaders().get(AppEngineHeaders.CLOUD_TRACE_CONTEXT));
+        builder.putAttributes(  AppEngineHeaders.CLOUD_TRACE_CONTEXT, headers.getHeaders().get(AppEngineHeaders.CLOUD_TRACE_CONTEXT));
         builder.setData(statusChangedTagsData);
 
         return builder.build();
diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/HeadersInfoGcpImpl.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/HeadersInfoGcpImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..4ed85ae2989a72be9cf5fd5a949a3cbf2715785c
--- /dev/null
+++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/HeadersInfoGcpImpl.java
@@ -0,0 +1,115 @@
+// Copyright 2017-2019, Schlumberger
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.opendes.indexer.gcp.util;
+
+import com.google.common.base.Strings;
+import org.opendes.client.api.DpsHeaders;
+import org.opendes.core.model.SlbHeaders;
+import org.opendes.core.util.IHeadersInfo;
+import org.opendes.core.util.Preconditions;
+import org.opendes.indexer.gcp.model.AppEngineHeaders;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class HeadersInfoGcpImpl implements IHeadersInfo {
+
+    @Autowired
+    private HttpHeaders httpHeaders;
+
+    private DpsHeaders headersMap = null;
+
+    private static final HashSet<String> FORBIDDEN_FROM_LOGGING = new HashSet<>();
+    static {
+        FORBIDDEN_FROM_LOGGING.add(DpsHeaders.AUTHORIZATION);
+        FORBIDDEN_FROM_LOGGING.add(DpsHeaders.ON_BEHALF_OF);
+    }
+
+    private static final HashSet<String> FORWARDED_HEADERS = new HashSet<>();
+    static {
+        FORWARDED_HEADERS.add(AppEngineHeaders.CITY_LAT_LONG);
+        FORWARDED_HEADERS.add(AppEngineHeaders.COUNTRY);
+        FORWARDED_HEADERS.add(AppEngineHeaders.REGION);
+        FORWARDED_HEADERS.add(AppEngineHeaders.CITY);
+        FORWARDED_HEADERS.add(AppEngineHeaders.TASK_QUEUE_RETRY_COUNT);
+        FORWARDED_HEADERS.add(AppEngineHeaders.TASK_QUEUE_NAME);
+        FORWARDED_HEADERS.add(AppEngineHeaders.DATA_GROUPS);
+        FORWARDED_HEADERS.add(AppEngineHeaders.CLOUD_TRACE_CONTEXT);
+        FORWARDED_HEADERS.add(AppEngineHeaders.TRACE_ID);
+        FORWARDED_HEADERS.add(AppEngineHeaders.CRON_SERVICE);
+        FORWARDED_HEADERS.add(SlbHeaders.PRIMARY_PARTITION_ID);
+    }
+
+    @Override
+    public DpsHeaders getHeaders() {
+        if (headersMap == null) {
+            headersMap = this.getCoreServiceHeaders(httpHeaders.toSingleValueMap());
+        }
+        return headersMap;
+    }
+
+    @Override
+    public String getUser() {
+        return getHeaders().getUserEmail();
+    }
+
+    @Override
+    public String getPartitionId() {
+        return getHeaders().getPartitionIdWithFallbackToAccountId();
+    }
+
+    @Override
+    public String getPrimaryPartitionId() {
+        return getHeadersMap().get(SlbHeaders.PRIMARY_PARTITION_ID);
+    }
+
+    @Override
+    public Map<String, String> getHeadersMap() {
+        return getHeaders().getHeaders();
+    }
+
+    @Override
+    public DpsHeaders getCoreServiceHeaders(Map<String, String> input) {
+        Preconditions.checkNotNull(input, "input headers cannot be null");
+
+        DpsHeaders output = DpsHeaders.createFromMap(input);
+        input.forEach((key,value) -> {
+            if (FORWARDED_HEADERS.contains(key)) {
+                if (key.equals(AppEngineHeaders.CLOUD_TRACE_CONTEXT)) {
+                    String traceContext = input.get(AppEngineHeaders.CLOUD_TRACE_CONTEXT);
+                    if (!Strings.isNullOrEmpty(traceContext)) {
+                        output.put(AppEngineHeaders.TRACE_ID, TraceIdExtractor.getTraceId(traceContext));
+                        output.put(key, traceContext);
+                    }
+                } else {
+                    if (!output.getHeaders().containsKey(key.toLowerCase())) {
+                        output.put(key, input.get(key));
+                    }
+                }
+            }
+        });
+        output.addCorrelationIdIfMissing();
+        return output;
+    }
+
+    @Override
+    public String toString() {
+        return this.getHeadersMap().entrySet().stream().filter(map -> !FORBIDDEN_FROM_LOGGING.contains(map.getKey().toLowerCase())).map(Map.Entry::toString).collect(Collectors.joining(" | "));
+    }
+
+}
\ No newline at end of file
diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/RequestInfoImpl.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/RequestInfoImpl.java
index 0a645c8f26b0c332db80d14caa21d29a55498c12..edc5f15bb1f055005fdeef772fe62122e44f3129 100644
--- a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/RequestInfoImpl.java
+++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/RequestInfoImpl.java
@@ -1,11 +1,15 @@
 package org.opendes.indexer.gcp.util;
 
 import com.google.common.base.Strings;
-import com.slb.core.model.AppEngineHeaders;
-import com.slb.core.model.DeploymentEnvironment;
-import com.slb.core.util.*;
 import org.apache.http.HttpStatus;
 import org.opendes.client.api.DpsHeaders;
+import org.opendes.client.multitenancy.TenantInfo;
+import org.opendes.core.model.DeploymentEnvironment;
+import org.opendes.core.util.AppException;
+import org.opendes.core.util.Config;
+import org.opendes.core.util.Constants;
+import org.opendes.core.util.HeadersInfo;
+import org.opendes.indexer.gcp.model.AppEngineHeaders;
 import org.opendes.indexer.util.IRequestInfo;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -18,7 +22,10 @@ public class RequestInfoImpl implements IRequestInfo {
     @Autowired
     private HeadersInfo headersInfo;
     @Autowired
-    private ServiceAccountJwtClient serviceAccountJwtClient;
+    private ServiceAccountJwtGcpClientImpl serviceAccountJwtClient;
+
+    @Autowired
+    private TenantInfo tenantInfo;
 
     private static final String expectedCronHeaderValue = "true";
 
@@ -64,7 +71,7 @@ public class RequestInfoImpl implements IRequestInfo {
         return queueId.endsWith(Constants.INDEXER_QUEUE_IDENTIFIER);
     }
 
-    private String checkOrGetAuthorizationHeader() {
+    public String checkOrGetAuthorizationHeader() {
         if (Config.getDeploymentEnvironment() == DeploymentEnvironment.LOCAL) {
             String authHeader = this.headersInfo.getHeaders().getAuthorization();
             if (Strings.isNullOrEmpty(authHeader)) {
@@ -72,7 +79,7 @@ public class RequestInfoImpl implements IRequestInfo {
             }
             return authHeader;
         } else {
-            return "Bearer " + this.serviceAccountJwtClient.getIdToken();
+            return "Bearer " + this.serviceAccountJwtClient.getIdToken(tenantInfo.getName());
         }
     }
 }
diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/ServiceAccountJwtGcpClientImpl.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/ServiceAccountJwtGcpClientImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..8e52738c46664d11cc87818bace77c5fa523e73a
--- /dev/null
+++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/ServiceAccountJwtGcpClientImpl.java
@@ -0,0 +1,181 @@
+// Copyright 2017-2019, Schlumberger
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.opendes.indexer.gcp.util;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.exceptions.JWTDecodeException;
+import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
+import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
+import com.google.api.client.http.HttpTransport;
+import com.google.api.client.json.JsonFactory;
+import com.google.api.client.json.jackson2.JacksonFactory;
+import com.google.api.services.iam.v1.Iam;
+import com.google.api.services.iam.v1.IamScopes;
+import com.google.api.services.iam.v1.model.SignJwtRequest;
+import com.google.api.services.iam.v1.model.SignJwtResponse;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpStatus;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ContentType;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+import org.opendes.client.api.DpsHeaders;
+import org.opendes.client.multitenancy.ITenantFactory;
+import org.opendes.client.multitenancy.TenantInfo;
+import org.opendes.core.cache.JwtCache;
+import org.opendes.core.logging.JaxRsDpsLog;
+import org.opendes.core.model.IdToken;
+import org.opendes.core.util.AppException;
+import org.opendes.core.util.IServiceAccountJwtClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ServiceAccountJwtGcpClientImpl implements IServiceAccountJwtClient {
+
+    private static final String JWT_AUDIENCE = "https://www.googleapis.com/oauth2/v4/token";
+    private static final String SERVICE_ACCOUNT_NAME_FORMAT = "projects/%s/serviceAccounts/%s";
+
+    private final JsonFactory JSON_FACTORY = new JacksonFactory();
+
+    private Iam iam;
+
+    @Autowired
+    private ITenantFactory tenantInfoServiceProvider;
+    @Autowired
+    private HeadersInfoGcpImpl headersInfoGcp;
+    @Autowired
+    private JwtCache cacheService;
+    @Autowired
+    private JaxRsDpsLog log;
+
+    @Value("${GOOGLE_AUDIENCES}")
+    public String GOOGLE_AUDIENCES;
+
+    @Value("${INDEXER_HOST}")
+    public String INDEXER_HOST;
+
+    public String getIdToken(String tenantName) {
+        this.log.info("Tenant name received for auth token is: " + tenantName);
+        TenantInfo tenant = this.tenantInfoServiceProvider.getTenantInfo(tenantName);
+        if (tenant == null) {
+            this.log.error("Invalid tenant name receiving from pubsub");
+            throw new AppException(HttpStatus.SC_BAD_REQUEST, "Invalid tenant Name", "Invalid tenant Name from pubsub");
+        }
+        try {
+
+            IdToken cachedToken = this.cacheService.get(tenant.getServiceAccount());
+            this.headersInfoGcp.getHeaders().put(DpsHeaders.USER_EMAIL, tenant.getServiceAccount());
+
+            if (!IdToken.refreshToken(cachedToken)) {
+                return cachedToken.getTokenValue();
+            }
+
+            // Getting signed JWT
+            Map<String, Object> signJwtPayload = this.getJWTCreationPayload(tenant);
+
+            SignJwtRequest signJwtRequest = new SignJwtRequest();
+            signJwtRequest.setPayload(JSON_FACTORY.toString(signJwtPayload));
+
+            String serviceAccountName = String.format(SERVICE_ACCOUNT_NAME_FORMAT, tenant.getProjectId(), tenant.getServiceAccount());
+
+            Iam.Projects.ServiceAccounts.SignJwt signJwt = this.getIam().projects().serviceAccounts().signJwt(serviceAccountName, signJwtRequest);
+            SignJwtResponse signJwtResponse = signJwt.execute();
+            String signedJwt = signJwtResponse.getSignedJwt();
+
+            // Getting id token
+            List<NameValuePair> postParameters = new ArrayList<>();
+            postParameters.add(new BasicNameValuePair("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"));
+            postParameters.add(new BasicNameValuePair("assertion", signedJwt));
+
+            HttpPost post = new HttpPost(JWT_AUDIENCE);
+            post.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType());
+            post.setEntity(new UrlEncodedFormEntity(postParameters, "UTF-8"));
+
+            try(CloseableHttpClient httpclient = HttpClients.createDefault();
+                CloseableHttpResponse httpResponse = httpclient.execute(post)) {
+                JsonObject jsonContent = new JsonParser().parse(EntityUtils.toString(httpResponse.getEntity())).getAsJsonObject();
+
+                if (!jsonContent.has("id_token")) {
+                    log.error(String.format("Google IAM response: %s", jsonContent.toString()));
+                    throw new AppException(HttpStatus.SC_FORBIDDEN, "Access denied", "The user is not authorized to perform this action");
+                }
+
+                String token = jsonContent.get("id_token").getAsString();
+                IdToken idToken = IdToken.builder().tokenValue(token).expirationTimeMillis(JWT.decode(token).getExpiresAt().getTime()).build();
+
+                this.cacheService.put(tenant.getServiceAccount(), idToken);
+
+                return token;
+            }
+        } catch (JWTDecodeException e) {
+            throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Persistence error", "Invalid token, error decoding", e);
+        } catch (AppException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Persistence error", "Error generating token", e);
+        }
+    }
+
+    public Iam getIam() throws Exception {
+
+        if (this.iam == null) {
+            HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
+
+            // Authenticate using Google Application Default Credentials.
+            GoogleCredential credential = GoogleCredential.getApplicationDefault();
+            if (credential.createScopedRequired()) {
+                List<String> scopes = new ArrayList<>();
+                // Enable full Cloud Platform scope.
+                scopes.add(IamScopes.CLOUD_PLATFORM);
+                credential = credential.createScoped(scopes);
+            }
+
+            // Create IAM API object associated with the authenticated transport.
+            this.iam = new Iam.Builder(httpTransport, JSON_FACTORY, credential)
+                    .setApplicationName(INDEXER_HOST)
+                    .build();
+        }
+
+        return this.iam;
+    }
+
+    private Map<String, Object> getJWTCreationPayload(TenantInfo tenantInfo) {
+
+        Map<String, Object> payload = new HashMap<>();
+        String googleAudience = GOOGLE_AUDIENCES;
+        if (googleAudience.contains(",")) {
+            googleAudience = googleAudience.split(",")[0];
+        }
+        payload.put("target_audience", googleAudience);
+        payload.put("exp", System.currentTimeMillis() / 1000 + 3600);
+        payload.put("iat", System.currentTimeMillis() / 1000);
+        payload.put("iss", tenantInfo.getServiceAccount());
+        payload.put("aud", JWT_AUDIENCE);
+
+        return payload;
+    }
+}
diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/TraceIdExtractor.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/TraceIdExtractor.java
new file mode 100644
index 0000000000000000000000000000000000000000..4d406e352bce61a242b823084e8b1ff3d79097bb
--- /dev/null
+++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/TraceIdExtractor.java
@@ -0,0 +1,63 @@
+// Copyright 2017-2019, Schlumberger
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.opendes.indexer.gcp.util;
+
+import com.google.common.base.Strings;
+import org.opendes.indexer.gcp.model.AppEngineHeaders;
+import org.springframework.util.MultiValueMap;
+
+import java.util.Random;
+import java.util.UUID;
+
+public class TraceIdExtractor {
+
+    /*
+     * "X-Cloud-Trace-Context: TRACE_ID/SPAN_ID;o=TRACE_TRUE" e.g. 105445aa7843bc8bf206b120001000/0;o=1"
+     * https://cloud.google.com/trace/docs/support
+     * */
+    public static String getTraceableCloudContext(MultiValueMap<String, String> requestHeaders) {
+        String traceContextHeader = requestHeaders.getFirst(AppEngineHeaders.CLOUD_TRACE_CONTEXT);
+
+        // get new if not found
+        if (Strings.isNullOrEmpty(traceContextHeader)) return getNewTraceContext();
+        // return as is
+        if (traceContextHeader.endsWith(";o=1")) return traceContextHeader;
+
+        String[] traceParts = traceContextHeader.split("[/;]");
+        // if there is only trace-id
+        if (traceParts.length == 1) return String.format("%s/%s;o=1", traceContextHeader, getNewSpanId());
+        // if trace-id and span-id
+        if (traceParts.length == 2) return String.format("%s;o=1", traceContextHeader);
+        // trace flag is turned off
+        return String.format("%s/%s;o=1", traceParts[0], traceParts[1]);
+    }
+
+    public static String getTraceId(String traceContextHeader) {
+        String[] traceParts = traceContextHeader.split("[/;]");
+        return traceParts.length > 0 ? traceParts[0] : getNewTraceId();
+    }
+
+    private static String getNewTraceContext() {
+        return String.format("%s/%s;o=1", getNewTraceId(), getNewSpanId());
+    }
+
+    private static String getNewTraceId() {
+        return UUID.randomUUID().toString().replaceAll("-", "");
+    }
+
+    private static String getNewSpanId() {
+        return Integer.toUnsignedString(new Random().nextInt());
+    }
+}
diff --git a/indexer-service-gcp/src/test/java/org/opendes/indexer/gcp/util/HeadersInfoGcpImplTest.java b/indexer-service-gcp/src/test/java/org/opendes/indexer/gcp/util/HeadersInfoGcpImplTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..0c2bd705dbadcef9ac69f1cc80d899523104d5bb
--- /dev/null
+++ b/indexer-service-gcp/src/test/java/org/opendes/indexer/gcp/util/HeadersInfoGcpImplTest.java
@@ -0,0 +1,198 @@
+// Copyright 2017-2019, Schlumberger
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.opendes.indexer.gcp.util;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.opendes.client.api.DpsHeaders;
+import org.opendes.core.model.SlbHeaders;
+import org.opendes.core.util.HeadersUtil;
+import org.opendes.indexer.gcp.model.AppEngineHeaders;
+import org.springframework.http.HttpHeaders;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static java.util.Collections.singletonList;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.when;
+
+@RunWith(SpringRunner.class)
+public class HeadersInfoGcpImplTest {
+
+    @Mock
+    private HttpHeaders httpHeaders;
+    @InjectMocks
+    private HeadersInfoGcpImpl sut;
+
+    @Test
+    public void should_convert_Cloud_Trace_when_header_contains_it() {
+        Map<String, String> requestHeaders = new HashMap<>();
+        requestHeaders.put(DpsHeaders.AUTHORIZATION, "any token");
+        requestHeaders.put(AppEngineHeaders.CLOUD_TRACE_CONTEXT, "any trace");
+
+        DpsHeaders map = this.sut.getCoreServiceHeaders(requestHeaders);
+
+        assertEquals("any token", map.getAuthorization());
+        assertEquals("any trace", map.getHeaders().get(AppEngineHeaders.TRACE_ID));
+    }
+
+    @Test
+    public void should_return_header_logs_when_header_contains_email() {
+        MultiValueMap<String, String> requestHeaders = new LinkedMultiValueMap<>();
+        requestHeaders.add(DpsHeaders.AUTHORIZATION, "any token");
+        requestHeaders.add(DpsHeaders.ON_BEHALF_OF, "any onBehalf");
+        requestHeaders.add(DpsHeaders.CORRELATION_ID, "any correlationId");
+        requestHeaders.add(DpsHeaders.USER_EMAIL, "abc@xyz.com");
+        requestHeaders.add(DpsHeaders.ACCOUNT_ID, "any account");
+
+        DpsHeaders map = this.sut.getCoreServiceHeaders(requestHeaders.toSingleValueMap());
+        assertEquals("any token", map.getAuthorization());
+        DpsHeaders headers = DpsHeaders.createFromEntrySet(requestHeaders.entrySet());
+
+        assertEquals("account id: any account | on behalf: any onBehalf | user email: any onBehalf | correlation id: " +
+                "any correlationId", HeadersUtil.toLogMsg(headers, "any onBehalf"));
+    }
+
+    @Test
+    public void should_return_header_logs_when_jwt_decode_fails() {
+        MultiValueMap<String, String> requestHeaders = new LinkedMultiValueMap<>();
+        requestHeaders.add(DpsHeaders.AUTHORIZATION, "any token");
+        requestHeaders.add(DpsHeaders.ON_BEHALF_OF, "any onBehalf");
+        requestHeaders.add(DpsHeaders.CORRELATION_ID, "any correlationId");
+        requestHeaders.add(DpsHeaders.ACCOUNT_ID, "any account");
+
+        DpsHeaders map = this.sut.getCoreServiceHeaders(requestHeaders.toSingleValueMap());
+
+        assertEquals("any token", map.getAuthorization());
+
+        DpsHeaders headers = DpsHeaders.createFromEntrySet(requestHeaders.entrySet());
+        assertEquals("account id: any account | on behalf: any onBehalf | correlation id: any correlationId",
+                HeadersUtil.toLogMsg(headers, null));
+    }
+
+    @Test
+    public void should_return_header_logs_when_header_doesNot_contain_email() {
+        Map<String, String> requestHeaders = new HashMap<>();
+        requestHeaders.put(DpsHeaders.AUTHORIZATION, "any token");
+        requestHeaders.put(DpsHeaders.ON_BEHALF_OF, "any onBehalf");
+        requestHeaders.put(DpsHeaders.CORRELATION_ID, "any correlationId");
+        requestHeaders.put(DpsHeaders.ACCOUNT_ID, "any account");
+
+        DpsHeaders map = this.sut.getCoreServiceHeaders(requestHeaders);
+        assertEquals("any token", map.getHeaders().get(DpsHeaders.AUTHORIZATION));
+    }
+
+    @Test
+    public void check_correct_headers() {
+        Map<String, String> requestHeaders = new HashMap<>();
+        requestHeaders.put(DpsHeaders.AUTHORIZATION, "any token");
+        requestHeaders.put(DpsHeaders.CORRELATION_ID, "any correlationId");
+        requestHeaders.put(DpsHeaders.ACCOUNT_ID, "any account");
+        requestHeaders.put(DpsHeaders.ON_BEHALF_OF, "any onBehalf");
+        requestHeaders.put(DpsHeaders.USER_EMAIL, "abc@xyz.com");
+        requestHeaders.put(DpsHeaders.CONTENT_TYPE, "any contentType");
+        requestHeaders.put(AppEngineHeaders.DATA_GROUPS, "any dataGrp");
+        requestHeaders.put(AppEngineHeaders.CRON_SERVICE, "true");
+
+        DpsHeaders map = this.sut.getCoreServiceHeaders(requestHeaders);
+
+        assertEquals("any token", map.getAuthorization());
+        assertEquals("any correlationId", map.getCorrelationId());
+        assertEquals("abc@xyz.com", map.getUserEmail());
+        assertEquals("any account", map.getPartitionIdWithFallbackToAccountId());
+        assertEquals("any onBehalf", map.getOnBehalfOf());
+        assertEquals("any contentType", map.getHeaders().get(DpsHeaders.CONTENT_TYPE));
+        assertEquals("any dataGrp", map.getHeaders().get(AppEngineHeaders.DATA_GROUPS));
+        assertEquals("true", map.getHeaders().get(AppEngineHeaders.CRON_SERVICE));
+    }
+
+    @Test
+    public void should_return_null_auth_header_when_invalid_header() {
+        Map<String, String> requestHeaders = new HashMap<>();
+        requestHeaders.put(DpsHeaders.AUTHORIZATION, null);
+
+        DpsHeaders map = this.sut.getCoreServiceHeaders(requestHeaders);
+
+        assertNotNull(map);
+        assertNull(map.getAuthorization());
+    }
+
+    @Test
+    public void should_addCorrelationId_when_gettingHeaders() {
+        MultiValueMap<String, String> requestHeaders = new LinkedMultiValueMap<>();
+        requestHeaders.put(DpsHeaders.USER_EMAIL, singletonList("a@b.com"));
+        when(httpHeaders.toSingleValueMap()).thenReturn(requestHeaders.toSingleValueMap());
+
+        assertNotNull(sut.getHeaders().getCorrelationId());
+    }
+
+    @Test
+    public void should_returnUser_when_requested() {
+        MultiValueMap<String, String> requestHeaders = new LinkedMultiValueMap<>();
+        requestHeaders.put(DpsHeaders.USER_EMAIL, singletonList("a@b.com"));
+        when(httpHeaders.toSingleValueMap()).thenReturn(requestHeaders.toSingleValueMap());
+
+        assertEquals("a@b.com", sut.getUser());
+    }
+
+    @Test
+    public void should_returnPrimaryAccountId_when_requested() {
+        MultiValueMap<String, String> requestHeaders = new LinkedMultiValueMap<>();
+        requestHeaders.put(SlbHeaders.PRIMARY_PARTITION_ID, singletonList("apc"));
+        when(httpHeaders.toSingleValueMap()).thenReturn(requestHeaders.toSingleValueMap());
+
+        assertEquals("apc", sut.getPrimaryPartitionId());
+    }
+
+    @Test
+    public void should_convert_to_string_when_map_is_correct() {
+        MultiValueMap<String, String> requestHeaders = new LinkedMultiValueMap<>();
+        requestHeaders.add("a", "a val");
+        requestHeaders.add("b", "b val");
+        requestHeaders.add(DpsHeaders.AUTHORIZATION, "blah");
+        when(this.httpHeaders.toSingleValueMap()).thenReturn(requestHeaders.toSingleValueMap());
+
+        assertFalse(this.sut.toString().contains("a=a val"));
+        assertFalse(this.sut.toString().contains("b=b val"));
+    }
+
+//    @Test
+//    public void should_convert_multivalued_map_to_hash_map_when_input_map_is_correct() {
+//        MultivaluedMap<String, String> requestHeaders = new MultivaluedMapImpl<String, String>();
+//        requestHeaders.putSingle(DpsHeaders.AUTHORIZATION, "any token");
+//        requestHeaders.putSingle(DpsHeaders.CORRELATION_ID, "any correlationId");
+//        requestHeaders.putSingle(DpsHeaders.ACCOUNT_ID, "any account");
+//        requestHeaders.putSingle(DpsHeaders.ON_BEHALF_OF, "any onBehalf");
+//        requestHeaders.putSingle(DpsHeaders.USER_EMAIL, "abc@xyz.com");
+//        requestHeaders.putSingle(DpsHeaders.CONTENT_TYPE, "any contentType");
+//        requestHeaders.putSingle(AppEngineHeaders.DATA_GROUPS, "any dataGrp");
+//
+//        Map<String, String> map = this.sut.convertMultiToRegularMap(requestHeaders);
+//
+//        assertEquals("any token", map.get(DpsHeaders.AUTHORIZATION));
+//        assertEquals("any correlationId", map.get(DpsHeaders.CORRELATION_ID));
+//        assertEquals("abc@xyz.com", map.get(DpsHeaders.USER_EMAIL));
+//        assertEquals("any account", map.get(DpsHeaders.ACCOUNT_ID));
+//        assertEquals("any onBehalf", map.get(DpsHeaders.ON_BEHALF_OF));
+//        assertEquals("any contentType", map.get(DpsHeaders.CONTENT_TYPE));
+//        assertEquals("any dataGrp", map.get(AppEngineHeaders.DATA_GROUPS));
+//    }
+}
\ No newline at end of file
diff --git a/indexer-service-gcp/src/test/java/org/opendes/indexer/gcp/util/ServiceAccountJwtGcpClientImplTest.java b/indexer-service-gcp/src/test/java/org/opendes/indexer/gcp/util/ServiceAccountJwtGcpClientImplTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ec298fe9b6f1d0b88e5751fced1088e9dd4125ee
--- /dev/null
+++ b/indexer-service-gcp/src/test/java/org/opendes/indexer/gcp/util/ServiceAccountJwtGcpClientImplTest.java
@@ -0,0 +1,178 @@
+// Copyright 2017-2019, Schlumberger
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.opendes.indexer.gcp.util;
+
+import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
+import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
+import com.google.api.client.http.javanet.NetHttpTransport;
+import com.google.api.services.iam.v1.Iam;
+import com.google.api.services.iam.v1.model.SignJwtResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.opendes.client.api.DpsHeaders;
+import org.opendes.client.multitenancy.TenantInfo;
+import org.opendes.core.cache.JwtCache;
+import org.opendes.core.logging.JaxRsDpsLog;
+import org.opendes.core.model.DeploymentEnvironment;
+import org.opendes.core.model.IdToken;
+import org.opendes.core.service.TenantInfoServiceImpl;
+import org.opendes.core.util.AppException;
+import org.opendes.core.util.Config;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.MockitoAnnotations.initMocks;
+import static org.powermock.api.mockito.PowerMockito.when;
+
+@Ignore
+@RunWith(SpringRunner.class)
+@PrepareForTest({GoogleNetHttpTransport.class, GoogleCredential.class, NetHttpTransport.class, SignJwtResponse.class, Iam.Builder.class, HttpClients.class, EntityUtils.class, Config.class})
+public class ServiceAccountJwtGcpClientImplTest {
+
+    private static final String JWT_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik1UVXlPREE0TXpFd09BPT0ifQ.eyJzdWIiOiJtemh1OUBzbGIuY29tIiwiaXNzIjoic2F1dGgtcHJldmlldy5zbGIuY29tIiwiYXVkIjoidGVzdC1zbGJkZXYtZGV2cG9ydGFsLnNsYmFwcC5jb20iLCJpYXQiOjE1MjgxNDg5MTUsImV4cCI6MTUyODIzNTMxNSwicHJvdmlkZXIiOiJzbGIuY29tIiwiY2xpZW50IjoidGVzdC1zbGJkZXYtZGV2cG9ydGFsLnNsYmFwcC5jb20iLCJ1c2VyaWQiOiJtemh1OUBzbGIuY29tIiwiZW1haWwiOiJtemh1OUBzbGIuY29tIiwiYXV0aHoiOiJ7XCJhY2NvdW50Q291bnRyeVwiOntcImNvZGVcIjpcInVzXCIsXCJpZFwiOjU3MTU5OTkxMDE4MTI3MzYsXCJuYW1lXCI6XCJVbml0ZWQgU3RhdGVzIG9mIEFtZXJpY2FcIn0sXCJhY2NvdW50SWRcIjo1NjkxODc4ODMzOTEzODU2LFwiYWNjb3VudE5hbWVcIjpcIlNJUyBJbnRlcm5hbCBIUVwiLFwiY3JlYXRlZFwiOlwiMjAxOC0wNS0wM1QxNzoyNTo1NS40NDNaXCIsXCJkZXBhcnRtZW50TWFuYWdlclwiOm51bGwsXCJzdWJzY3JpcHRpb25zXCI6W3tcImFjY291bnRJZFwiOjU2OTE4Nzg4MzM5MTM4NTYsXCJjb250cmFjdElkXCI6NTc1MTcwMDIxMjE1NDM2OCxcImNyZWF0ZWRcIjpcIjIwMTgtMDUtMDNUMTc6MzM6MDkuNTczWlwiLFwiY3JtQ29udHJhY3RJZFwiOlwiU0lTLUlOVEVSTkFMLUhRLVFBXCIsXCJjcm1Db250cmFjdEl0ZW1JZFwiOlwiZGV2bGlcIixcImV4cGlyYXRpb25cIjpcIjE5NzAtMDEtMDFUMDA6MDA6MDAuMDAwWlwiLFwiaWRcIjo1MDc5Mjg4NTA0MTIzMzkyLFwicHJvZHVjdFwiOntcImNvZGVcIjpcImRldmVsb3Blci1saWdodFwiLFwiY29tY2F0TmFtZVwiOlwiTm90IGluIENvbUNhdFwiLFwiZmVhdHVyZVNldHNcIjpbe1wiYXBwbGljYXRpb25cIjp7XCJjb2RlXCI6XCJhcGlkZXZlbG9wZXJwb3J0YWxcIixcImlkXCI6NTE2ODkzMDY5NTkzODA0OCxcIm5hbWVcIjpcIkFQSSBEZXZlbG9wZXIgUG9ydGFsXCIsXCJ0eXBlXCI6XCJXZWJBcHBcIn0sXCJjbGFpbXNcIjpudWxsLFwiaWRcIjo1MTkxNTcyMjg3MTI3NTUyLFwibmFtZVwiOlwiRGV2ZWxvcGVyXCIsXCJ0eXBlXCI6XCJCQVNFXCJ9XSxcImlkXCI6NTE1MDczMDE1MTI2NDI1NixcIm5hbWVcIjpcIkRldmVsb3BlciBQb3J0YWxcIixcInBhcnROdW1iZXJcIjpcIlNERUwtUEItU1VCVVwifX1dLFwidXNlckVtYWlsXCI6XCJtemh1OUBzbGIuY29tXCIsXCJ1c2VyTmFtZVwiOlwiTWluZ3lhbmcgWmh1XCJ9XG4iLCJsYXN0bmFtZSI6IlpodSIsImZpcnN0bmFtZSI6Ik1pbmd5YW5nIiwiY291bnRyeSI6IiIsImNvbXBhbnkiOiIiLCJqb2J0aXRsZSI6IiIsInN1YmlkIjoiNDE3YjczMjktYmMwNy00OTFmLWJiYzQtZTQ1YjRhMWFiYjVjLVd3U0c0dyIsImlkcCI6ImNvcnAyIiwiaGQiOiJzbGIuY29tIn0.WQfGr1Xu-6IdaXdoJ9Fwzx8O2el1UkFPWo1vk_ujiAfdOjAR46UG5SrBC7mzC7gYRyK3a4fimBmbv3uRVJjTNXdxXRLZDw0SvXUMIOqjUGLom491ESbrtka_Xz7vGO-tWyDcEQDTfFzQ91LaVN7XdzL18_EDTXZoPhKb-zquyk9WLQxP9Mw-3Yh-UrbvC9nl1-GRn1IVbzp568kqkpOVUFM9alYSGw-oMGDZNt1DIYOJnpGaw2RB5B3AKvNivZH_Xdac7ZTzQbsDOt8B8DL2BphuxcJ9jshCJkM2SHQ15uErv8sfnzMwdF08e_0QcC_30I8eX9l8yOu6TnwwqlXunw";
+
+    @Mock
+    private JaxRsDpsLog log;
+    @Mock
+    private GoogleCredential credential;
+    @Mock
+    private NetHttpTransport httpTransport;
+    @Mock
+    private SignJwtResponse signJwtResponse;
+    @Mock
+    private Iam iam;
+    @Mock
+    private Iam.Projects iamProject;
+    @Mock
+    private Iam.Projects.ServiceAccounts iamProjectServiceAccounts;
+    @Mock
+    private Iam.Projects.ServiceAccounts.SignJwt signJwt;
+    @Mock
+    private CloseableHttpClient httpClient;
+    @Mock
+    private CloseableHttpResponse httpResponse;
+    @InjectMocks
+    private TenantInfoServiceImpl tenantInfoServiceProvider;
+    @Mock
+    private TenantInfoServiceImpl tenantInfoService;
+    @Mock
+    private JwtCache cacheService;
+    @Mock
+    private HeadersInfoGcpImpl headersInfoGcp;
+    @InjectMocks @Spy
+    private ServiceAccountJwtGcpClientImpl sut;
+    @Before
+    public void setup() throws Exception {
+        initMocks(this);
+
+//        mockStatic(GoogleNetHttpTransport.class);
+//        mockStatic(GoogleCredential.class);
+//        mockStatic(HttpClients.class);
+//        mockStatic(EntityUtils.class);
+//        mockStatic(Config.class);
+
+        when(GoogleNetHttpTransport.newTrustedTransport()).thenReturn(httpTransport);
+        when(GoogleCredential.getApplicationDefault()).thenReturn(credential);
+        when(credential.createScopedRequired()).thenReturn(true);
+        when(credential.createScoped(any())).thenReturn(credential);
+        when(HttpClients.createDefault()).thenReturn(httpClient);
+        when(httpClient.execute(any())).thenReturn(httpResponse);
+        when(Config.getDeploymentEnvironment()).thenReturn(DeploymentEnvironment.LOCAL);
+        when(Config.getGoogleAudiences()).thenReturn("aud");
+
+        when(this.tenantInfoServiceProvider).thenReturn(this.tenantInfoService);
+        
+        TenantInfo tenantInfo = new TenantInfo();
+        tenantInfo.setServiceAccount("tenant");
+        when(this.tenantInfoService.getTenantInfo()).thenReturn(tenantInfo);
+
+        when(this.sut.getIam()).thenReturn(iam);
+        when(this.iam.projects()).thenReturn(iamProject);
+        when(this.iamProject.serviceAccounts()).thenReturn(iamProjectServiceAccounts);
+        when(this.iamProjectServiceAccounts.signJwt(any(), any())).thenReturn(signJwt);
+        when(this.signJwt.execute()).thenReturn(signJwtResponse);
+        when(this.signJwtResponse.getSignedJwt()).thenReturn("testJwt");
+
+        Map<String, String> headers = new HashMap<>();
+        DpsHeaders dpsHeaders = DpsHeaders.createFromMap(headers);
+        when(this.headersInfoGcp.getHeaders()).thenReturn(dpsHeaders);
+    }
+
+    @Test
+    public void should_returnCachedToken_givenCachedToken_getIdTokenTest() {
+        String tokenValue = "tokenValue";
+        IdToken idToken = IdToken.builder().tokenValue(tokenValue).expirationTimeMillis(System.currentTimeMillis() + 10000000L).build();
+        when(this.cacheService.get(any())).thenReturn(idToken);
+
+        String returnedIdToken = this.sut.getIdToken(tokenValue);
+
+        Assert.assertEquals(tokenValue, returnedIdToken);
+    }
+
+    @Test
+    public void should_returnValidToken_getIdTokenTest() throws Exception {
+        when(EntityUtils.toString(any())).thenReturn(String.format("{\"id_token\":\"%s\"}", JWT_TOKEN));
+
+        String returnedToken = this.sut.getIdToken("tenant");
+
+        Assert.assertEquals(JWT_TOKEN, returnedToken);
+    }
+
+    @Test
+    public void should_return500_given_invalidJWTResponse_getIdTokenException() {
+        try {
+            when(EntityUtils.toString(any())).thenReturn(String.format("{\"id_token\":\"%s\"}", "invalid jwt"));
+
+            this.sut.getIdToken("tenant");
+            fail("Should throw exception");
+        } catch (AppException e) {
+            Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getError().getCode());
+            Assert.assertEquals("Invalid token, error decoding", e.getError().getMessage());
+        } catch (Exception e) {
+            fail("Should not throw this exception" + e.getMessage());
+        }
+    }
+
+    @Test
+    public void should_return403_given_missingIdTokenResponse_getIdTokenException() {
+        try {
+            when(EntityUtils.toString(any())).thenReturn("{}");
+
+            this.sut.getIdToken("tenant");
+            fail("Should throw exception");
+        } catch (AppException e) {
+            Assert.assertEquals(HttpStatus.SC_FORBIDDEN, e.getError().getCode());
+            Assert.assertEquals("The user is not authorized to perform this action", e.getError().getMessage());
+        } catch (Exception e) {
+            fail("Should not throw this exception" + e.getMessage());
+        }
+    }
+}
diff --git a/indexer-service-gcp/src/test/java/org/opendes/indexer/gcp/util/TraceIdExtractorTest.java b/indexer-service-gcp/src/test/java/org/opendes/indexer/gcp/util/TraceIdExtractorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..09cf784b63ea2a5aea559d7de72081f75547ea87
--- /dev/null
+++ b/indexer-service-gcp/src/test/java/org/opendes/indexer/gcp/util/TraceIdExtractorTest.java
@@ -0,0 +1,71 @@
+// Copyright 2017-2019, Schlumberger
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.opendes.indexer.gcp.util;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.opendes.client.api.DpsHeaders;
+import org.opendes.indexer.gcp.model.AppEngineHeaders;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static junit.framework.TestCase.assertTrue;
+
+@RunWith(SpringRunner.class)
+public class TraceIdExtractorTest {
+
+    @Test
+    public void should_getTraceableCloudContext_when_header_doesNot_contains_it() {
+        List<String> token = new ArrayList<>();
+        token.add("any token");
+        MultiValueMap<String, String> requestHeaders = new LinkedMultiValueMap<>();
+        requestHeaders.put(DpsHeaders.AUTHORIZATION, token);
+
+        String actual = TraceIdExtractor.getTraceableCloudContext(requestHeaders);
+
+        assertTrue(actual.matches(".*o=1"));
+    }
+
+    @Test
+    public void should_getTraceableCloudContext_when_header_contains_it() {
+        List<String> token = new ArrayList<>();
+        token.add("any token");
+        List<String> trace = new ArrayList<>();
+        trace.add("any trace");
+        MultiValueMap<String, String> requestHeaders = new LinkedMultiValueMap<>();
+        requestHeaders.put(DpsHeaders.AUTHORIZATION, token);
+        requestHeaders.put(AppEngineHeaders.CLOUD_TRACE_CONTEXT, trace);
+
+        String actual = TraceIdExtractor.getTraceableCloudContext(requestHeaders);
+        assertTrue(actual.matches("any trace/.*o=1"));
+
+        trace = new ArrayList<>();
+        trace.add("any/trace");
+        requestHeaders.put(AppEngineHeaders.CLOUD_TRACE_CONTEXT, trace);
+        actual = TraceIdExtractor.getTraceableCloudContext(requestHeaders);
+        assertTrue(actual.matches("any/trace;o=1"));
+
+        trace = new ArrayList<>();
+        trace.add("any/trace/test");
+        requestHeaders.put(AppEngineHeaders.CLOUD_TRACE_CONTEXT, trace);
+        actual = TraceIdExtractor.getTraceableCloudContext(requestHeaders);
+        assertTrue(actual.matches("any/trace;o=1"));
+    }
+
+}
diff --git a/indexer-service-gcp/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/indexer-service-gcp/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000000000000000000000000000000000000..ca6ee9cea8ec189a088d50559325d4e84ff8ad09
--- /dev/null
+++ b/indexer-service-gcp/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
\ No newline at end of file
diff --git a/indexer-service-root/pom.xml b/indexer-service-root/pom.xml
index 8736a330674b9d55b99fae0bda4aed78db43da2e..6b0143bdbb4adb8175aa54ca15e54ed2b36fc0af 100644
--- a/indexer-service-root/pom.xml
+++ b/indexer-service-root/pom.xml
@@ -33,14 +33,23 @@
 			<artifactId>indexer-search-core-root</artifactId>
 			<version>1.0.0</version>
 		</dependency>
+
 		<!-- spring boot dependencies -->
 		<dependency>
-			<groupId>org.springframework.boot</groupId>
-			<artifactId>spring-boot-starter-actuator</artifactId>
+			<groupId>org.apache.tomcat.embed</groupId>
+			<artifactId>tomcat-embed-core</artifactId>
+			<version>9.0.21</version>
 		</dependency>
+
 		<dependency>
 			<groupId>org.springframework.boot</groupId>
-			<artifactId>spring-boot-starter-data-redis</artifactId>
+			<artifactId>spring-boot-starter-web</artifactId>
+			<exclusions>
+				<exclusion>
+					<groupId>org.springframework.boot</groupId>
+					<artifactId>spring-boot-starter-tomcat</artifactId>
+				</exclusion>
+			</exclusions>
 		</dependency>
 		<dependency>
 			<groupId>org.springframework.boot</groupId>
@@ -48,7 +57,7 @@
 		</dependency>
 		<dependency>
 			<groupId>org.springframework.boot</groupId>
-			<artifactId>spring-boot-starter-oauth2-client</artifactId>
+			<artifactId>spring-boot-starter-actuator</artifactId>
 		</dependency>
 		<dependency>
 			<groupId>org.springframework.boot</groupId>
@@ -56,15 +65,7 @@
 		</dependency>
 		<dependency>
 			<groupId>org.springframework.boot</groupId>
-			<artifactId>spring-boot-starter-web</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.springframework.cloud</groupId>
-			<artifactId>spring-cloud-starter-oauth2</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.springframework.cloud</groupId>
-			<artifactId>spring-cloud-starter-security</artifactId>
+			<artifactId>spring-boot-starter-validation</artifactId>
 		</dependency>
 
 		<dependency>
@@ -83,12 +84,6 @@
 			<version>6.6.2</version>
 		</dependency>
 
-
-		<dependency>
-			<groupId>org.springframework.boot</groupId>
-			<artifactId>spring-boot-starter-tomcat</artifactId>
-			<scope>provided</scope>
-		</dependency>
 		<dependency>
 			<groupId>com.google.guava</groupId>
 			<artifactId>guava</artifactId>
@@ -126,18 +121,6 @@
 			</exclusions>
 		</dependency>
 
-		<!-- https://mvnrepository.com/artifact/io.github.resilience4j/resilience4j-circuitbreaker -->
-		<dependency>
-			<groupId>io.github.resilience4j</groupId>
-			<artifactId>resilience4j-circuitbreaker</artifactId>
-			<version>0.17.0</version>
-		</dependency>
-		<!-- https://mvnrepository.com/artifact/io.github.resilience4j/resilience4j-retry -->
-		<dependency>
-			<groupId>io.github.resilience4j</groupId>
-			<artifactId>resilience4j-retry</artifactId>
-			<version>0.17.0</version>
-		</dependency>
 		<!-- https://mvnrepository.com/artifact/commons-lang/commons-lang -->
 		<dependency>
 			<groupId>commons-lang</groupId>
@@ -157,133 +140,6 @@
 			<version>${springfox-version}</version>
 		</dependency>
 
-		<!-- Test Dependencies -->
-		<dependency>
-			<groupId>org.springframework.boot</groupId>
-			<artifactId>spring-boot-starter-test</artifactId>
-			<scope>test</scope>
-		</dependency>
-		<dependency>
-			<groupId>org.springframework.security</groupId>
-			<artifactId>spring-security-test</artifactId>
-			<scope>test</scope>
-		</dependency>
-
-
-		<!-- Test Dependencies -->
-		<dependency>
-			<groupId>junit</groupId>
-			<artifactId>junit</artifactId>
-			<version>4.12</version>
-			<scope>test</scope>
-		</dependency>
-		<dependency>
-			<groupId>org.hamcrest</groupId>
-			<artifactId>hamcrest-junit</artifactId>
-			<version>2.0.0.0</version>
-			<scope>test</scope>
-		</dependency>
-		<!-- https://mvnrepository.com/artifact/org.powermock/powermock-api-mockito2 -->
-		<dependency>
-			<groupId>org.powermock</groupId>
-			<artifactId>powermock-api-mockito2</artifactId>
-			<version>2.0.2</version>
-			<scope>test</scope>
-		</dependency>
-		<!-- https://mvnrepository.com/artifact/org.powermock/powermock-module-junit4 -->
-		<dependency>
-			<groupId>org.powermock</groupId>
-			<artifactId>powermock-module-junit4</artifactId>
-			<version>2.0.2</version>
-			<scope>test</scope>
-		</dependency>
-		<dependency>
-			<groupId>org.mockito</groupId>
-			<artifactId>mockito-core</artifactId>
-			<version>3.0.0</version>
-			<scope>test</scope>
-		</dependency>
-		<dependency>
-			<groupId>org.codehaus.mojo</groupId>
-			<artifactId>cobertura-maven-plugin</artifactId>
-			<version>2.7</version>
-			<scope>test</scope>
-		</dependency>
-
-		<!-- https://mvnrepository.com/artifact/com.github.stefanbirkner/system-rules -->
-		<dependency>
-			<groupId>com.github.stefanbirkner</groupId>
-			<artifactId>system-rules</artifactId>
-			<version>1.2.0</version>
-			<scope>test</scope>
-		</dependency>
-        <dependency>
-            <groupId>com.google.api.grpc</groupId>
-            <artifactId>proto-google-cloud-pubsub-v1</artifactId>
-            <version>1.60.0</version>
-            <scope>compile</scope>
-        </dependency>
-		<dependency>
-			<groupId>com.google.api.grpc</groupId>
-			<artifactId>proto-google-cloud-pubsub-v1</artifactId>
-			<version>1.60.0</version>
-			<scope>compile</scope>
-		</dependency>
-		<dependency>
-			<groupId>com.google.api.grpc</groupId>
-			<artifactId>proto-google-cloud-pubsub-v1</artifactId>
-			<version>1.60.0</version>
-			<scope>compile</scope>
-		</dependency>
-        <dependency>
-            <groupId>org.opendes.core</groupId>
-            <artifactId>indexer-search-core-root</artifactId>
-            <version>1.0.0</version>
-        </dependency>
-		<dependency>
-			<groupId>org.opendes.core</groupId>
-			<artifactId>indexer-search-core-root</artifactId>
-			<version>1.0.0</version>
-		</dependency>
-		<dependency>
-			<groupId>org.opendes.core</groupId>
-			<artifactId>indexer-search-core-root</artifactId>
-			<version>1.0.0</version>
-		</dependency>
 	</dependencies>
 
-	<dependencyManagement>
-		<dependencies>
-			<dependency>
-				<groupId>org.springframework.cloud</groupId>
-				<artifactId>spring-cloud-dependencies</artifactId>
-				<version>${spring-cloud.version}</version>
-				<type>pom</type>
-				<scope>import</scope>
-			</dependency>
-		</dependencies>
-	</dependencyManagement>
-
-<!--	<build>-->
-<!--		<plugins>-->
-<!--			<plugin>-->
-<!--				<groupId>org.springframework.boot</groupId>-->
-<!--				<artifactId>spring-boot-maven-plugin</artifactId>-->
-<!--				<executions>-->
-<!--					<execution>-->
-<!--						<goals>-->
-<!--							<goal>repackage</goal>-->
-<!--						</goals>-->
-<!--						<configuration>-->
-<!--							<classifier>spring-boot</classifier>-->
-<!--							<mainClass>-->
-<!--								org.opendes.indexer.IndexerApplication-->
-<!--							</mainClass>-->
-<!--						</configuration>-->
-<!--					</execution>-->
-<!--				</executions>-->
-<!--			</plugin>-->
-<!--		</plugins>-->
-<!--	</build>-->
-
 </project>
diff --git a/indexer-service-root/src/main/java/org/opendes/indexer/SwaggerDoc.java b/indexer-service-root/src/main/java/org/opendes/indexer/SwaggerDoc.java
index d0c9a387a220dd8f406253a2650f3a61a00251b0..4894e1df574b927c0402aa2c10786786528765ac 100644
--- a/indexer-service-root/src/main/java/org/opendes/indexer/SwaggerDoc.java
+++ b/indexer-service-root/src/main/java/org/opendes/indexer/SwaggerDoc.java
@@ -145,4 +145,8 @@ public final class SwaggerDoc {
 
     // REQUEST VALIDATION
     public static final String REQUEST_VALIDATION_NOT_NULL_BODY = "Request body can not be null";
+
+    // Azure Schema Request
+    public static final String SCHEMA_REQUEST_KIND = "Record kind for which the schema information is applied to.";
+    public static final String RECORD_KIND_EXAMPLE = "common:welldb:wellbore:1.0.0";
 }
diff --git a/indexer-service-root/src/main/java/org/opendes/indexer/model/PublishMessage.java b/indexer-service-root/src/main/java/org/opendes/indexer/model/PublishMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..280958b2efe9bd4af043f2722e4e5014c3b44e77
--- /dev/null
+++ b/indexer-service-root/src/main/java/org/opendes/indexer/model/PublishMessage.java
@@ -0,0 +1,5 @@
+package org.opendes.indexer.model;
+
+public class PublishMessage {
+
+}
diff --git a/indexer-service-root/src/main/java/org/opendes/indexer/publish/IPublisher.java b/indexer-service-root/src/main/java/org/opendes/indexer/publish/IPublisher.java
index 6c592cfba6fd838e7d262ffc6e9790bcf9508edb..ef8709d9f6f65e0931c9f8d3ea890da8c1644fa4 100644
--- a/indexer-service-root/src/main/java/org/opendes/indexer/publish/IPublisher.java
+++ b/indexer-service-root/src/main/java/org/opendes/indexer/publish/IPublisher.java
@@ -1,10 +1,9 @@
 package org.opendes.indexer.publish;
 
-import com.google.pubsub.v1.PubsubMessage;
 import org.opendes.client.api.DpsHeaders;
 import org.opendes.indexer.util.JobStatus;
 
 public interface IPublisher {
 
-    public PubsubMessage publishStatusChangedTagsToTopic(DpsHeaders headers, JobStatus indexerBatchStatus) throws Exception;
+    public void publishStatusChangedTagsToTopic(DpsHeaders headers, JobStatus indexerBatchStatus) throws Exception;
 }
diff --git a/indexer-service-root/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/indexer-service-root/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000000000000000000000000000000000000..ca6ee9cea8ec189a088d50559325d4e84ff8ad09
--- /dev/null
+++ b/indexer-service-root/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 2cf8e36ae6c3e867b89eee55dd635a605ebb5f2f..151d85522e1fd6c95d270a9dd25817f5392a13bb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,7 +37,66 @@
 <!--        <springfox-version>2.7.0</springfox-version>-->
     </properties>
 
+    <repositories>
+        <repository>
+            <id>dev-azure-com-slb-des-ext-collaboration-os-core</id>
+            <url>https://pkgs.dev.azure.com/slb-des-ext-collaboration/_packaging/os-core/maven/v1</url>
+            <releases>
+                <enabled>true</enabled>
+            </releases>
+            <snapshots>
+                <enabled>true</enabled>
+            </snapshots>
+        </repository>
+    </repositories>
 
+    <!-- Test Dependencies -->
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+            <scope>test</scope>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/org.powermock/powermock-api-mockito2 -->
+        <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-api-mockito2</artifactId>
+            <version>2.0.2</version>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- https://mvnrepository.com/artifact/org.powermock/powermock-module-junit4 -->
+        <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-module-junit4</artifactId>
+            <version>2.0.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>3.0.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.mojo</groupId>
+            <artifactId>cobertura-maven-plugin</artifactId>
+            <version>2.7</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
 
     <profiles>
         <profile>