/*
* 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.addthis.hydra.query;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.InetSocketAddress;
import com.addthis.basis.jvm.Shutdown;
import com.addthis.basis.util.LessStrings;
import com.addthis.codec.config.Configs;
import com.addthis.hydra.data.query.source.MeshQuerySource;
import com.addthis.hydra.data.query.source.SearchRunner;
import com.addthis.hydra.query.web.QueryServer;
import com.addthis.meshy.MeshyServer;
import com.addthis.meshy.MeshyServerGroup;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.yammer.metrics.reporting.MetricsServlet;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A sort of wrapper class for mesh query workers; similar to {@link QueryServer} but this one only starts up a
* jetty server to display metrics and then starts up meshy as if meshy was the class whose main method was invoked.
* <p/>
* For query worker's actual query processing logic, {@link MeshQuerySource}.
*/
public class MeshQueryWorker implements AutoCloseable {
private static final Logger log = LoggerFactory.getLogger(MeshQueryWorker.class);
/**
* The port on which the worker reports metrics. The default is fine unless at some point we want multiple
* mqworkers on one machine. If that happens, use codec to give them different ports.
*/
private final int webPort;
private final MeshyServer meshyServer;
/** server listens for query requests using HTML protoc */
private final transient Server htmlQueryServer;
public static void main(String[] args) throws Exception {
Shutdown.createWithShutdownHook(() -> {
try {
return Configs.newDefault(MeshQueryWorker.class);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}, MeshQueryWorker::close);
}
@JsonCreator
private MeshQueryWorker(@JsonProperty(value = "webPort", required = true) int webPort,
@JsonProperty(value = "bindAddress", required = true) String bindAddress,
@JsonProperty(value = "root", required = true) File root,
@JsonProperty(value = "peers", required = true) String peers) throws Exception {
// start jetty for metrics servlet
this.webPort = webPort;
this.htmlQueryServer = startHtmlQueryServer();
String[] portInfo = LessStrings.splitArray(bindAddress, ":");
int portNum = Integer.parseInt(portInfo[0]);
@Nullable String[] netIf;
if (portInfo.length > 1) {
netIf = new String[portInfo.length - 1];
System.arraycopy(portInfo, 1, netIf, 0, netIf.length);
} else {
netIf = null;
}
this.meshyServer = new MeshyServer(portNum, root, netIf, new MeshyServerGroup());
for (String peer : LessStrings.splitArray(peers, ",")) {
String[] hostPort = LessStrings.splitArray(peer, ":");
int port;
if (hostPort.length > 1) {
port = Integer.parseInt(hostPort[1]);
} else {
port = meshyServer.getLocalAddress().getPort();
}
meshyServer.connectPeer(new InetSocketAddress(hostPort[0], port));
}
log.info("[init] web port={}, mesh server={}", webPort, meshyServer);
}
@Override public void close() {
SearchRunner.shutdownSearchPool();
meshyServer.close();
try {
htmlQueryServer.stop();
} catch (Throwable e) {
log.error("Error closing http server for mqworker", e);
}
}
/** Starts the jetty server, makes the metrics servlet, and registers it with jetty */
private Server startHtmlQueryServer() throws Exception {
Server newHtmlServer = new Server(webPort);
//Using a servlet as a handler requires a lot of boilerplate
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
newHtmlServer.setHandler(context);
//Actually create the servlet (from yammer metrics)
context.addServlet(new ServletHolder(new MetricsServlet()), "/metrics");
Connector connector0 = newHtmlServer.getConnectors()[0];
connector0.setMaxIdleTime(600000);
newHtmlServer.start();
return newHtmlServer;
}
}