/*
* Copyright 2014 Alexey Plotnik
*
* 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.stem;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import org.glassfish.grizzly.filterchain.FilterChainContext;
import org.glassfish.grizzly.filterchain.NextAction;
import org.glassfish.grizzly.http.HttpBaseFilter;
import org.glassfish.grizzly.http.HttpContent;
import org.glassfish.grizzly.http.HttpRequestPacket;
import org.glassfish.grizzly.http.server.*;
import org.glassfish.grizzly.threadpool.ThreadPoolConfig;
import org.glassfish.jersey.server.ContainerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.stem.coordination.ZooException;
import org.stem.coordination.ZookeeperClient;
import org.stem.coordination.ZookeeperFactoryCached;
import org.stem.coordination.ZookeeperPaths;
import org.stem.domain.Cluster;
import org.stem.exceptions.DefaultExceptionMapper;
import org.stem.exceptions.StemException;
import org.stem.exceptions.StemExceptionMapper;
import org.stem.net.CLStaticByPassHttpHandler;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
public class ClusterManagerDaemon {
private static final Logger logger = LoggerFactory.getLogger(ClusterManagerDaemon.class);
private static final String STEM_CONFIG_PROPERTY = "stem.cluster.config";
private static final String DEFAULT_CONFIG = "cluster.yaml";
public static final String RESOURCES_PACKAGE = "org.stem.api.resources";
static Config config;
private ZookeeperClient zookeeperClient;
static {
applyConfig(loadConfig());
}
private static void applyConfig(Config config) {
logger.info("Zookeeper address: {}", config.zookeeper_endpoint);
logger.info("REST API listen on {}", config.web_listen_address);
}
public static String zookeeperEndpoint() {
return config.zookeeper_endpoint;
}
public static Config loadConfig() {
URL url = getConfigUrl();
logger.info("Loading settings from " + url);
InputStream stream;
try {
stream = url.openStream();
} catch (IOException e) {
throw new AssertionError(e);
}
Constructor constructor = new Constructor(Config.class);
Yaml yaml = new Yaml(constructor);
config = (Config) yaml.load(stream);
return config;
}
static URL getConfigUrl() {
String configPath = System.getProperty(STEM_CONFIG_PROPERTY);
if (null == configPath)
configPath = DEFAULT_CONFIG;
URL url;
try {
File file = new File(configPath);
url = file.toURI().toURL();
url.openStream().close();
} catch (Exception e) {
ClassLoader loader = ClusterManagerDaemon.class.getClassLoader();
url = loader.getResource(configPath);
if (null == url)
throw new RuntimeException("Cannot load " + configPath + ". Ensure \"" + STEM_CONFIG_PROPERTY + "\" system property is set correctly.");
}
return url;
}
private HttpServer server;
public static void main(String[] args) throws InterruptedException {
environmentDetect();
ClusterManagerDaemon daemon = new ClusterManagerDaemon();
daemon.start();
Thread.currentThread().join();
}
private static void environmentDetect() {
String javaVersion = System.getProperty("java.version");
String javaVmName = System.getProperty("java.vm.name");
logger.info("Java Virtual Machine: {}/{}", javaVmName, javaVersion);
logger.info("Heap size: {}/{}", Runtime.getRuntime().totalMemory(), Runtime.getRuntime().maxMemory());
logger.info("Classpath: {}", System.getProperty("java.class.path"));
}
public void start() {
try {
connectToZookeeper();
initZookeeperPaths();
loadClusterConfiguration();
configureWebServer();
startWebServer();
} catch (Exception e) {
throw new RuntimeException("Failed to start cluster manager", e);
}
}
private void loadClusterConfiguration() throws Exception {
logger.info("Try to load cluster configuration from Zookeeper");
Cluster.instance.load();
}
private void configureWebServer() throws URISyntaxException, IOException {
ResourceConfig resourceCfg = new ResourceConfig();
setupJsonSerialization(resourceCfg);
resourceCfg.packages(RESOURCES_PACKAGE);
resourceCfg.registerClasses(StemExceptionMapper.class);
resourceCfg.registerClasses(DefaultExceptionMapper.class);
server = createServer(webListenAddress(), resourceCfg);
configureStaticResources(server, resourceCfg);
}
private URI webListenAddress() throws URISyntaxException {
return new URI("http://" + config.web_listen_address);
}
private void startWebServer() throws IOException {
server.start();
server.getListener("grizzly").getFileCache().setEnabled(false);
// Monkey patching to rewrite URL without nginx
server.getListener("grizzly").getFilterChain().add(2, new HttpBaseFilter() // TODO: indexOfType
{
@Override
public NextAction handleRead(FilterChainContext ctx) throws IOException {
HttpContent message = ctx.getMessage();
HttpRequestPacket httpHeader = (HttpRequestPacket) message.getHttpHeader();
String uri = httpHeader.getRequestURI();
if (uri.equals("/")) {
uri = "/admin/index.html";
httpHeader.getRequestURIRef().init(uri.getBytes(), 0, uri.getBytes().length);
}
return super.handleRead(ctx);
}
});
}
public void stop() {
if (null != server) // TODO: why this may happen
server.shutdownNow();
Cluster.instance.destroy();
}
private void configureStaticResources(HttpServer server, ResourceConfig resourceCfg) {
HttpHandler handler = getStaticHandler();
server.getServerConfiguration().addHttpHandler(handler, "/admin", "/static");
}
private void connectToZookeeper() {
logger.info("Establishing connection to Zookeeper cluster ({})...", config.zookeeper_endpoint);
try {
zookeeperClient = ZookeeperFactoryCached.newClient(config.zookeeper_endpoint);
logger.info("Connected to Zookeeper endpoint {}", config.zookeeper_endpoint);
} catch (ZooException e) {
throw new StemException("Failed to connect to Zookeeper", e);
} catch (Exception e) {
throw new StemException("Error while creating Zookeeper client", e);
}
}
private void initZookeeperPaths() {
logger.info("Initialize Zookeeper z-node hierarchy");
try {
for (String path : ZookeeperPaths.containerNodes()) {
zookeeperClient.createIfNotExists(path);
logger.info("z-node '{}' initialized", path);
}
//client.createIfNotExists(ZooConstants.CLUSTER_DESCRIPTOR_PATH);
} catch (ZooException e) {
throw new StemException("Failed connect to Zookeeper", e);
} catch (Exception e) {
throw new StemException("Can not initialize Zookeeper paths", e);
}
}
private HttpServer createServer(URI uri, ResourceConfig resourceCfg) {
final String host = uri.getHost();
final int port = uri.getPort();
final HttpServer server = new HttpServer();
final NetworkListener listener = new NetworkListener("grizzly", host, port);
listener.getTransport().setWorkerThreadPoolConfig(adjustThreadPool());
server.addListener(listener);
final ServerConfiguration config = server.getServerConfiguration();
final HttpHandler handler = ContainerFactory.createContainer(HttpHandler.class, resourceCfg);
if (handler != null)
config.addHttpHandler(handler, uri.getPath());
return server;
}
private void setupJsonSerialization(ResourceConfig cfg) {
cfg.registerInstances(getJsonProvider());
}
public static JacksonJaxbJsonProvider getJsonProvider() {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//mapper.getSerializationConfig().addMixInAnnotations(File.class, MixIn_File.class);
JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider(mapper, JacksonJaxbJsonProvider.DEFAULT_ANNOTATIONS);
provider.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, Boolean.TRUE);
provider.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, Boolean.FALSE);
return provider;
}
private ThreadPoolConfig adjustThreadPool() {
Integer corePoolSize = 2;
Integer maxPoolSize = 8;
ThreadPoolConfig workerPoolConfig = ThreadPoolConfig.defaultConfig();
workerPoolConfig.setCorePoolSize(corePoolSize);
workerPoolConfig.setMaxPoolSize(maxPoolSize);
workerPoolConfig.setPoolName("Worker");
return workerPoolConfig;
}
public HttpHandler getStaticHandler() {
if (null != System.getProperty("development")) {
// User can update static content on-fly in development mode
logger.warn("Initialize static resources in development mode");
return new StaticHttpHandler("src/main/resources/static/");
} else {
logger.info("Initialize static resources in production mode");
CLStaticByPassHttpHandler handler = new CLStaticByPassHttpHandler(this.getClass().getClassLoader(), "static/");
handler.setFileCacheEnabled(false);
return handler;
}
}
}