001/* 002 * (C) Copyright 2017 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * bdelbosc 018 */ 019package org.nuxeo.elasticsearch.core; 020 021import java.io.Closeable; 022import java.io.IOException; 023import java.net.BindException; 024import java.nio.file.Files; 025import java.nio.file.Path; 026import java.nio.file.Paths; 027import java.util.Collection; 028import java.util.Collections; 029import java.util.List; 030import java.util.stream.Collectors; 031 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034import org.elasticsearch.common.settings.Settings; 035import org.elasticsearch.node.Node; 036import org.elasticsearch.node.NodeValidationException; 037import org.elasticsearch.plugins.Plugin; 038import org.elasticsearch.transport.Netty4Plugin; 039import org.nuxeo.common.utils.ExceptionUtils; 040import org.nuxeo.ecm.core.api.NuxeoException; 041import org.nuxeo.elasticsearch.config.ElasticSearchEmbeddedServerConfig; 042import org.nuxeo.runtime.api.Framework; 043 044/** 045 * @since 9.3 046 */ 047public class ElasticSearchEmbeddedNode implements Closeable { 048 private static final Log log = LogFactory.getLog(ElasticSearchEmbeddedNode.class); 049 050 private static final int DEFAULT_RETRY = 3; 051 052 protected final ElasticSearchEmbeddedServerConfig config; 053 054 protected Node node; 055 056 protected int retry = DEFAULT_RETRY; 057 058 public ElasticSearchEmbeddedNode(ElasticSearchEmbeddedServerConfig config) { 059 this.config = config; 060 } 061 062 public void start() { 063 log.info("Starting embedded (in JVM) Elasticsearch"); 064 if (!Framework.isTestModeSet()) { 065 log.warn("Elasticsearch embedded configuration is ONLY for testing" 066 + " purpose. You need to create a dedicated Elasticsearch" + " cluster for production."); 067 } 068 Settings.Builder buidler = Settings.builder(); 069 buidler.put("http.enabled", config.httpEnabled()) 070 .put("network.host", config.getNetworkHost()) 071 .put("path.home", config.getHomePath()) 072 .put("path.data", config.getDataPath()) 073 .put("cluster.name", config.getClusterName()) 074 .put("node.name", config.getNodeName()) 075 .put("http.netty.worker_count", 4) 076 .put("http.cors.enabled", true) 077 .put("http.cors.allow-origin", "*") 078 .put("http.cors.allow-credentials", true) 079 .put("http.cors.allow-headers", "Authorization, X-Requested-With, Content-Type, Content-Length") 080 .put("cluster.routing.allocation.disk.threshold_enabled", false) 081 .put("http.port", config.getHttpPort()); 082 if (config.getIndexStorageType() != null) { 083 buidler.put("index.store.type", config.getIndexStorageType()); 084 } 085 Settings settings = buidler.build(); 086 log.debug("Using settings: " + settings.toDelimitedString(',')); 087 088 Collection<Class<? extends Plugin>> plugins = Collections.singletonList(Netty4Plugin.class); 089 try { 090 node = new PluginConfigurableNode(settings, plugins); 091 node.start(); 092 // try with another home path 093 config.setHomePath(null); 094 } catch (NodeValidationException e) { 095 throw new NuxeoException("Cannot start embedded Elasticsearch: " + e.getMessage(), e); 096 } catch (Exception e) { 097 Throwable cause = ExceptionUtils.getRootCause(e); 098 if (cause != null && cause instanceof BindException) { 099 retry--; 100 log.error(String.format("Cannot bind local Elasticsearch on port %s, from %s, retry countdown: %d", 101 config.getHttpPort(), config.getDataPath(), retry)); 102 try { 103 node.close(); 104 Thread.sleep(5000); 105 } catch (InterruptedException e1) { 106 Thread.currentThread().interrupt(); 107 throw new NuxeoException(e1); 108 } catch (IOException e1) { 109 throw new NuxeoException(e1); 110 } 111 if (retry <= 0) { 112 String msg = "Not able to bind to local Elasticsearch after multiple attempts, give up"; 113 log.error(msg); 114 throw new IllegalStateException(msg); 115 } 116 start(); 117 } else { 118 throw e; 119 } 120 } 121 retry = DEFAULT_RETRY; 122 log.debug("Elasticsearch node started."); 123 } 124 125 @Override 126 public void close() throws IOException { 127 log.info("Closing embedded (in JVM) Elasticsearch"); 128 node.close(); 129 // TODO: should we delete the lock ? 130 // deleteLuceneFileLock(config.getDataPath()); 131 log.info("Node closed: " + node.isClosed()); 132 node = null; 133 } 134 135 protected void deleteLuceneFileLock(String root) throws IOException { 136 List<Path> locks = Files.walk(Paths.get(root)) 137 .filter(f -> f.getFileName().toString().equals("node.lock")) 138 .collect(Collectors.toList()); 139 if (!locks.isEmpty()) { 140 locks.forEach(f -> log.warn("Found lock on close, deleting: " + f)); 141 locks.forEach(f -> f.toFile().delete()); 142 } 143 } 144 145 public ElasticSearchEmbeddedServerConfig getConfig() { 146 return config; 147 } 148 149 public Node getNode() { 150 return node; 151 } 152}