/* * Copyright 2016 The Simple File Server Authors * * 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.sfs.nodes; import com.google.common.net.HostAndPort; import io.vertx.core.MultiMap; import io.vertx.core.Vertx; import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientRequest; import io.vertx.core.http.HttpClientResponse; import io.vertx.core.logging.Logger; import io.vertx.core.logging.LoggerFactory; import org.apache.http.HttpHeaders; import org.sfs.Server; import org.sfs.VertxContext; import org.sfs.jobs.Jobs; import org.sfs.rx.BufferToJsonObject; import org.sfs.rx.Defer; import org.sfs.rx.HttpClientKeepAliveResponseBodyBuffer; import org.sfs.rx.ObservableFuture; import org.sfs.rx.RxHelper; import org.sfs.rx.ToVoid; import org.sfs.util.HttpBodyLogger; import org.sfs.util.HttpClientRequestAndResponse; import org.sfs.util.HttpClientResponseHeaderLogger; import rx.Observable; import java.util.Collection; import java.util.concurrent.TimeUnit; import static com.google.common.io.BaseEncoding.base64; import static java.net.HttpURLConnection.HTTP_CONFLICT; import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import static java.net.HttpURLConnection.HTTP_OK; import static org.sfs.jobs.Jobs.Parameters.JOB_ID; import static org.sfs.util.SfsHttpHeaders.X_SFS_REMOTE_NODE_TOKEN; public class RemoteMasterNode implements MasterNode { private static final Logger LOGGER = LoggerFactory.getLogger(RemoteMasterNode.class); private final Vertx vertx; private final Collection<HostAndPort> hostAndPorts; private final int responseTimeout; private final String remoteNodeSecret; private final HttpClient httpClient; private final Nodes nodes; public RemoteMasterNode(VertxContext<Server> vertxContext, int responseTimeout, Collection<HostAndPort> hostAndPorts) { this.responseTimeout = responseTimeout; this.hostAndPorts = hostAndPorts; this.remoteNodeSecret = base64().encode(vertxContext.verticle().getRemoteNodeSecret()); this.httpClient = vertxContext.verticle().httpClient(false); this.vertx = vertxContext.vertx(); this.nodes = vertxContext.verticle().nodes(); } @Override public Observable<Void> executeJob(String jobId, MultiMap params, long timeout, TimeUnit timeUnit) { return Defer.aVoid() .flatMap(aVoid -> nodes.connectFirstAvailable( vertx, hostAndPorts, hostAndPort -> { ObservableFuture<HttpClientResponse> handler = RxHelper.observableFuture(); String url = String.format("http://%s/_internal_node_master_execute_job/", hostAndPort.toString()); HttpClientRequest httpClientRequest = httpClient .postAbs(url, httpClientResponse -> { httpClientResponse.pause(); handler.complete(httpClientResponse); }) .exceptionHandler(handler::fail) .putHeader(X_SFS_REMOTE_NODE_TOKEN, remoteNodeSecret) .putHeader(JOB_ID, jobId) .putHeader(HttpHeaders.TIMEOUT, String.valueOf(timeUnit.toMillis(timeout))) .setTimeout(responseTimeout); httpClientRequest.headers().addAll(params); httpClientRequest.end(); return handler.map(httpClientResponse -> new HttpClientRequestAndResponse(httpClientRequest, httpClientResponse)); })) .map(HttpClientRequestAndResponse::getResponse) .map(new HttpClientResponseHeaderLogger()) .flatMap(httpClientResponse -> Defer.just(httpClientResponse) .flatMap(new HttpClientKeepAliveResponseBodyBuffer()) .map(new HttpBodyLogger()) .map(buffer -> { if (HTTP_OK != httpClientResponse.statusCode()) { throw new HttpClientResponseException(httpClientResponse, buffer); } return buffer; }) .map(new BufferToJsonObject()) .map(jsonObject -> { Integer code = jsonObject.getInteger("code"); if (code != null) { if (HTTP_OK == code) { return jsonObject; } else if (HTTP_CONFLICT == code) { return new Jobs.JobAlreadyRunning(); } else if (HTTP_NOT_FOUND == code) { throw new Jobs.JobNotFound(); } } throw new HttpClientResponseException(httpClientResponse, jsonObject); })) .map(new ToVoid<>()); } @Override public Observable<Void> waitForJob(String jobId, long timeout, TimeUnit timeUnit) { return Defer.aVoid() .flatMap(aVoid -> nodes.connectFirstAvailable( vertx, hostAndPorts, hostAndPort -> { ObservableFuture<HttpClientResponse> handler = RxHelper.observableFuture(); String url = String.format("http://%s/_internal_node_master_wait_for_job/", hostAndPort.toString()); HttpClientRequest httpClientRequest = httpClient .postAbs(url, httpClientResponse -> { httpClientResponse.pause(); handler.complete(httpClientResponse); }) .exceptionHandler(handler::fail) .putHeader(X_SFS_REMOTE_NODE_TOKEN, remoteNodeSecret) .putHeader(JOB_ID, jobId) .putHeader(HttpHeaders.TIMEOUT, String.valueOf(timeUnit.toMillis(timeout))) .setTimeout(responseTimeout); httpClientRequest.end(); return handler.map(httpClientResponse -> new HttpClientRequestAndResponse(httpClientRequest, httpClientResponse)); })) .map(HttpClientRequestAndResponse::getResponse) .map(new HttpClientResponseHeaderLogger()) .flatMap(httpClientResponse -> Defer.just(httpClientResponse) .flatMap(new HttpClientKeepAliveResponseBodyBuffer()) .map(new HttpBodyLogger()) .map(buffer -> { if (HTTP_OK != httpClientResponse.statusCode()) { throw new HttpClientResponseException(httpClientResponse, buffer); } return buffer; }) .map(new BufferToJsonObject()) .map(jsonObject -> { Integer code = jsonObject.getInteger("code"); if (code != null) { if (HTTP_OK == code) { return jsonObject; } else if (HTTP_CONFLICT == code) { return new Jobs.WaitStoppedExpired(); } else if (HTTP_NOT_FOUND == code) { throw new Jobs.JobNotFound(); } } throw new HttpClientResponseException(httpClientResponse, jsonObject); })) .map(new ToVoid<>()); } @Override public Observable<Void> stopJob(String jobId, long timeout, TimeUnit timeUnit) { return Defer.aVoid() .flatMap(aVoid -> nodes.connectFirstAvailable( vertx, hostAndPorts, hostAndPort -> { ObservableFuture<HttpClientResponse> handler = RxHelper.observableFuture(); String url = String.format("http://%s/_internal_node_master_stop_job/", hostAndPort.toString()); HttpClientRequest httpClientRequest = httpClient .postAbs(url, httpClientResponse -> { httpClientResponse.pause(); handler.complete(httpClientResponse); }) .exceptionHandler(handler::fail) .putHeader(X_SFS_REMOTE_NODE_TOKEN, remoteNodeSecret) .putHeader(JOB_ID, jobId) .putHeader(HttpHeaders.TIMEOUT, String.valueOf(timeUnit.toMillis(timeout))) .setTimeout(responseTimeout); httpClientRequest.end(); return handler.map(httpClientResponse -> new HttpClientRequestAndResponse(httpClientRequest, httpClientResponse)); })) .map(HttpClientRequestAndResponse::getResponse) .map(new HttpClientResponseHeaderLogger()) .flatMap(httpClientResponse -> Defer.just(httpClientResponse) .flatMap(new HttpClientKeepAliveResponseBodyBuffer()) .map(new HttpBodyLogger()) .map(buffer -> { if (HTTP_OK != httpClientResponse.statusCode()) { throw new HttpClientResponseException(httpClientResponse, buffer); } return buffer; }) .map(new BufferToJsonObject()) .map(jsonObject -> { Integer code = jsonObject.getInteger("code"); if (code != null) { if (HTTP_OK == code) { return jsonObject; } else if (HTTP_CONFLICT == code) { return new Jobs.WaitStoppedExpired(); } else if (HTTP_NOT_FOUND == code) { throw new Jobs.JobNotFound(); } } throw new HttpClientResponseException(httpClientResponse, jsonObject); })) .map(new ToVoid<>()); } }