blob: c2a0c6df1fe294166e31ea72187d42a484efa9b3 [file] [log] [blame]
// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
import com.ericsson.gerrit.plugins.highavailability.Configuration;
import com.ericsson.gerrit.plugins.highavailability.cache.Constants;
import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.HttpResponseHandler.HttpResult;
import com.google.common.base.Joiner;
import com.google.common.base.Supplier;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.server.events.Event;
import com.google.gerrit.server.events.SupplierSerializer;
import com.google.gson.GsonBuilder;
import com.google.inject.Inject;
import java.io.IOException;
import javax.net.ssl.SSLException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class RestForwarder implements Forwarder {
private static final Logger log = LoggerFactory.getLogger(RestForwarder.class);
private final HttpSession httpSession;
private final String pluginRelativePath;
private final Configuration cfg;
@Inject
RestForwarder(HttpSession httpClient, @PluginName String pluginName, Configuration cfg) {
this.httpSession = httpClient;
this.pluginRelativePath = Joiner.on("/").join("/plugins", pluginName);
this.cfg = cfg;
}
@Override
public boolean indexAccount(final int accountId) {
return new Request("index account", accountId) {
@Override
HttpResult send() throws IOException {
return httpSession.post(
Joiner.on("/").join(pluginRelativePath, "index/account", accountId));
}
}.execute();
}
@Override
public boolean indexChange(final String projectName, final int changeId) {
return new Request("index change", changeId) {
@Override
HttpResult send() throws IOException {
return httpSession.post(buildIndexEndpoint(projectName, changeId));
}
}.execute();
}
@Override
public boolean deleteChangeFromIndex(final int changeId) {
return new Request("delete change", changeId) {
@Override
HttpResult send() throws IOException {
return httpSession.delete(buildIndexEndpoint(changeId));
}
}.execute();
}
@Override
public boolean indexGroup(final String uuid) {
return new Request("index group", uuid) {
@Override
HttpResult send() throws IOException {
return httpSession.post(Joiner.on("/").join(pluginRelativePath, "index/group", uuid));
}
}.execute();
}
private String buildIndexEndpoint(int changeId) {
return buildIndexEndpoint("", changeId);
}
private String buildIndexEndpoint(String projectName, int changeId) {
String escapedProjectName = Url.encode(projectName);
return Joiner.on("/")
.join(pluginRelativePath, "index/change", escapedProjectName + '~' + changeId);
}
@Override
public boolean send(final Event event) {
return new Request("send event", event.type) {
@Override
HttpResult send() throws IOException {
String serializedEvent =
new GsonBuilder()
.registerTypeAdapter(Supplier.class, new SupplierSerializer())
.create()
.toJson(event);
return httpSession.post(Joiner.on("/").join(pluginRelativePath, "event"), serializedEvent);
}
}.execute();
}
@Override
public boolean evict(final String cacheName, final Object key) {
return new Request("invalidate cache " + cacheName, key) {
@Override
HttpResult send() throws IOException {
String json = GsonParser.toJson(cacheName, key);
return httpSession.post(Joiner.on("/").join(pluginRelativePath, "cache", cacheName), json);
}
}.execute();
}
@Override
public boolean addToProjectList(String projectName) {
return new Request("Update project_list, add ", projectName) {
@Override
HttpResult send() throws IOException {
return httpSession.post(buildProjectListEndpoint(projectName));
}
}.execute();
}
@Override
public boolean removeFromProjectList(String projectName) {
return new Request("Update project_list, remove ", projectName) {
@Override
HttpResult send() throws IOException {
return httpSession.delete(buildProjectListEndpoint(projectName));
}
}.execute();
}
private String buildProjectListEndpoint(String projectName) {
return Joiner.on("/")
.join(pluginRelativePath, "cache", Constants.PROJECT_LIST, Url.encode(projectName));
}
private abstract class Request {
private final String action;
private final Object key;
private int execCnt;
Request(String action, Object key) {
this.action = action;
this.key = key;
}
boolean execute() {
log.debug("Executing {} {}", action, key);
for (; ; ) {
try {
execCnt++;
tryOnce();
log.debug("{} {} OK", action, key);
return true;
} catch (ForwardingException e) {
int maxTries = cfg.http().maxTries();
log.debug("Failed to {} {} [{}/{}]", action, key, execCnt, maxTries, e);
if (!e.isRecoverable()) {
log.error("{} {} failed with unrecoverable error; giving up", action, key, e);
return false;
}
if (execCnt >= maxTries) {
log.error("Failed to {} {} after {} tries; giving up", action, key, maxTries);
return false;
}
log.debug("Retrying to {} {}", action, key);
try {
Thread.sleep(cfg.http().retryInterval());
} catch (InterruptedException ie) {
log.error("{} {} was interrupted; giving up", action, key, ie);
Thread.currentThread().interrupt();
return false;
}
}
}
}
void tryOnce() throws ForwardingException {
try {
HttpResult result = send();
if (!result.isSuccessful()) {
throw new ForwardingException(
true, String.format("Unable to %s %s : %s", action, key, result.getMessage()));
}
} catch (IOException e) {
throw new ForwardingException(isRecoverable(e), e.getMessage(), e);
}
}
abstract HttpResult send() throws IOException;
boolean isRecoverable(IOException e) {
return !(e instanceof SSLException);
}
}
}