|
| 1 | +/* |
| 2 | + * SPDX-License-Identifier: Apache-2.0 |
| 3 | + * |
| 4 | + * The OpenSearch Contributors require contributions made to |
| 5 | + * this file be licensed under the Apache-2.0 license or a |
| 6 | + * compatible open source license. |
| 7 | + */ |
| 8 | + |
| 9 | +package org.opensearch.upgrade; |
| 10 | + |
| 11 | +import com.fasterxml.jackson.databind.ObjectMapper; |
| 12 | +import org.opensearch.Version; |
| 13 | +import org.opensearch.cli.Terminal; |
| 14 | +import org.opensearch.common.SuppressForbidden; |
| 15 | +import org.opensearch.common.collect.Tuple; |
| 16 | +import org.opensearch.common.settings.Settings; |
| 17 | + |
| 18 | +import java.io.File; |
| 19 | +import java.io.IOException; |
| 20 | +import java.net.HttpURLConnection; |
| 21 | +import java.net.URL; |
| 22 | +import java.nio.file.Path; |
| 23 | +import java.util.ArrayList; |
| 24 | +import java.util.Collections; |
| 25 | +import java.util.HashMap; |
| 26 | +import java.util.List; |
| 27 | +import java.util.Map; |
| 28 | +import java.util.Optional; |
| 29 | +import java.util.Scanner; |
| 30 | + |
| 31 | +/** |
| 32 | + * Looks for an existing elasticsearch installation. First it tries to identify automatically, |
| 33 | + * and if unsuccessful, asks the user to input the missing details. |
| 34 | + * <p> |
| 35 | + * If an elasticsearch installation can not be found, throws a runtime error which fails the |
| 36 | + * upgrade task. |
| 37 | + */ |
| 38 | +class DetectEsInstallationTask implements UpgradeTask { |
| 39 | + private static final int ES_DEFAULT_PORT = 9200; |
| 40 | + private static final String ES_CONFIG_ENV = "ES_PATH_CONF"; |
| 41 | + private static final String ES_CONFIG_YML = "elasticsearch.yml"; |
| 42 | + private static final String ES_HOME = "ES_HOME"; |
| 43 | + |
| 44 | + @SuppressForbidden(reason = "We need to read external es config files") |
| 45 | + @Override |
| 46 | + public void accept(final Tuple<TaskInput, Terminal> input) { |
| 47 | + final TaskInput taskInput = input.v1(); |
| 48 | + final Terminal terminal = input.v2(); |
| 49 | + try { |
| 50 | + terminal.println("Looking for an elasticsearch installation ..."); |
| 51 | + String esHomeEnv = System.getenv(ES_HOME); |
| 52 | + if (esHomeEnv == null) { |
| 53 | + esHomeEnv = terminal.readText("Missing ES_HOME env variable, enter the path to elasticsearch home: "); |
| 54 | + if (esHomeEnv == null || esHomeEnv.isEmpty()) { |
| 55 | + throw new RuntimeException("Invalid input for path to elasticsearch home directory."); |
| 56 | + } |
| 57 | + } |
| 58 | + taskInput.setEsHome(new File(esHomeEnv).toPath()); |
| 59 | + |
| 60 | + String esConfEnv = System.getenv(ES_CONFIG_ENV); |
| 61 | + if (esConfEnv == null) { |
| 62 | + esConfEnv = terminal.readText("Missing ES_PATH_CONF env variable, enter the path to elasticsearch config directory: "); |
| 63 | + if (esConfEnv == null || esHomeEnv.isEmpty()) { |
| 64 | + throw new RuntimeException("Invalid input for path to elasticsearch config directory."); |
| 65 | + } |
| 66 | + } |
| 67 | + taskInput.setEsConfig(new File(esConfEnv).toPath()); |
| 68 | + |
| 69 | + final Settings esSettings = Settings.builder().loadFromPath(taskInput.getEsConfig().resolve(ES_CONFIG_YML)).build(); |
| 70 | + final String url = retrieveUrl(esSettings); |
| 71 | + taskInput.setBaseUrl(url); |
| 72 | + final boolean running = isRunning(url); |
| 73 | + taskInput.setRunning(running); |
| 74 | + if (running) { |
| 75 | + terminal.println("Found a running instance of elasticsearch at " + url); |
| 76 | + taskInput.setRunning(true); |
| 77 | + try { |
| 78 | + updateTaskInput(taskInput, fetchInfoFromUrl(taskInput.getBaseUrl())); |
| 79 | + } catch (RuntimeException e) { |
| 80 | + updateTaskInput(taskInput, fetchInfoFromEsSettings(esSettings)); |
| 81 | + } |
| 82 | + try { |
| 83 | + taskInput.setPlugins(fetchPluginsFromUrl(taskInput.getBaseUrl())); |
| 84 | + } catch (RuntimeException e) { |
| 85 | + taskInput.setPlugins(detectPluginsFromEsHome(taskInput.getEsHome())); |
| 86 | + } |
| 87 | + } else { |
| 88 | + terminal.println("Did not find a running instance of elasticsearch at " + url); |
| 89 | + updateTaskInput(taskInput, fetchInfoFromEsSettings(esSettings)); |
| 90 | + taskInput.setPlugins(detectPluginsFromEsHome(taskInput.getEsHome())); |
| 91 | + } |
| 92 | + } catch (IOException e) { |
| 93 | + throw new RuntimeException("Error detecting existing elasticsearch installation. " + e); |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | + @SuppressWarnings("unchecked") |
| 98 | + private void updateTaskInput(TaskInput taskInput, Map<?, ?> response) { |
| 99 | + final Map<String, String> versionMap = (Map<String, String>) response.get("version"); |
| 100 | + if (versionMap != null) { |
| 101 | + final String vStr = versionMap.get("number"); |
| 102 | + if (vStr != null) { |
| 103 | + taskInput.setVersion(Version.fromString(vStr)); |
| 104 | + } |
| 105 | + } |
| 106 | + taskInput.setNode((String) response.get("name")); |
| 107 | + taskInput.setCluster((String) response.get("cluster_name")); |
| 108 | + } |
| 109 | + |
| 110 | + // package private for unit testing |
| 111 | + String retrieveUrl(final Settings esSettings) { |
| 112 | + final int port = Optional.ofNullable(esSettings.get("http.port")).map(this::extractPort).orElse(ES_DEFAULT_PORT); |
| 113 | + return "http://localhost:" + port; |
| 114 | + } |
| 115 | + |
| 116 | + private Integer extractPort(final String port) { |
| 117 | + try { |
| 118 | + return Integer.parseInt(port.trim()); |
| 119 | + } catch (Exception ex) { |
| 120 | + return ES_DEFAULT_PORT; |
| 121 | + } |
| 122 | + } |
| 123 | + |
| 124 | + @SuppressForbidden(reason = "Need to connect to http endpoint for elasticsearch.") |
| 125 | + private boolean isRunning(final String url) { |
| 126 | + try { |
| 127 | + final URL esUrl = new URL(url); |
| 128 | + final HttpURLConnection conn = (HttpURLConnection) esUrl.openConnection(); |
| 129 | + conn.setRequestMethod("GET"); |
| 130 | + conn.setConnectTimeout(1000); |
| 131 | + conn.connect(); |
| 132 | + return conn.getResponseCode() == 200; |
| 133 | + } catch (IOException e) { |
| 134 | + return false; |
| 135 | + } |
| 136 | + } |
| 137 | + |
| 138 | + @SuppressForbidden(reason = "Retrieve information on the installation.") |
| 139 | + private Map<?, ?> fetchInfoFromUrl(final String url) { |
| 140 | + try { |
| 141 | + final URL esUrl = new URL(url); |
| 142 | + final HttpURLConnection conn = (HttpURLConnection) esUrl.openConnection(); |
| 143 | + conn.setRequestMethod("GET"); |
| 144 | + conn.setConnectTimeout(1000); |
| 145 | + conn.connect(); |
| 146 | + |
| 147 | + final StringBuilder json = new StringBuilder(); |
| 148 | + final Scanner scanner = new Scanner(esUrl.openStream()); |
| 149 | + while (scanner.hasNext()) { |
| 150 | + json.append(scanner.nextLine()); |
| 151 | + } |
| 152 | + scanner.close(); |
| 153 | + final ObjectMapper mapper = new ObjectMapper(); |
| 154 | + return mapper.readValue(json.toString(), Map.class); |
| 155 | + } catch (IOException e) { |
| 156 | + throw new RuntimeException("Error retrieving elasticsearch cluster info, " + e); |
| 157 | + } |
| 158 | + } |
| 159 | + |
| 160 | + private Map<?, ?> fetchInfoFromEsSettings(final Settings esSettings) throws IOException { |
| 161 | + final Map<String, String> info = new HashMap<>(); |
| 162 | + final String node = esSettings.get("node.name") != null ? esSettings.get("node.name") : "unknown"; |
| 163 | + final String cluster = esSettings.get("cluster.name") != null ? esSettings.get("cluster.name") : "unknown"; |
| 164 | + info.put("name", node); |
| 165 | + info.put("cluster_name", cluster); |
| 166 | + return info; |
| 167 | + } |
| 168 | + |
| 169 | + @SuppressWarnings("unchecked") |
| 170 | + @SuppressForbidden(reason = "Retrieve information on installed plugins.") |
| 171 | + private List<String> fetchPluginsFromUrl(final String url) { |
| 172 | + final List<String> plugins = new ArrayList<>(); |
| 173 | + try { |
| 174 | + final URL esUrl = new URL(url + "/_cat/plugins?format=json&local=true"); |
| 175 | + final HttpURLConnection conn = (HttpURLConnection) esUrl.openConnection(); |
| 176 | + conn.setRequestMethod("GET"); |
| 177 | + conn.setConnectTimeout(1000); |
| 178 | + conn.connect(); |
| 179 | + if (conn.getResponseCode() == 200) { |
| 180 | + final StringBuilder json = new StringBuilder(); |
| 181 | + final Scanner scanner = new Scanner(esUrl.openStream()); |
| 182 | + while (scanner.hasNext()) { |
| 183 | + json.append(scanner.nextLine()); |
| 184 | + } |
| 185 | + scanner.close(); |
| 186 | + final ObjectMapper mapper = new ObjectMapper(); |
| 187 | + final Map<String, String>[] response = mapper.readValue(json.toString(), Map[].class); |
| 188 | + for (Map<String, String> plugin : response) { |
| 189 | + plugins.add(plugin.get("component")); |
| 190 | + } |
| 191 | + } |
| 192 | + return plugins; |
| 193 | + } catch (IOException e) { |
| 194 | + throw new RuntimeException("Error retrieving elasticsearch plugin details, " + e); |
| 195 | + } |
| 196 | + } |
| 197 | + |
| 198 | + private List<String> detectPluginsFromEsHome(final Path esHome) { |
| 199 | + // list out the contents of the plugins directory under esHome |
| 200 | + return Collections.emptyList(); |
| 201 | + } |
| 202 | +} |
0 commit comments