|
| 1 | +/* |
| 2 | + * Licensed to Elasticsearch under one or more contributor |
| 3 | + * license agreements. See the NOTICE file distributed with |
| 4 | + * this work for additional information regarding copyright |
| 5 | + * ownership. Elasticsearch licenses this file to you under |
| 6 | + * the Apache License, Version 2.0 (the "License"); you may |
| 7 | + * not use this file except in compliance with the License. |
| 8 | + * You may obtain a copy of the License at |
| 9 | + * |
| 10 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | + * |
| 12 | + * Unless required by applicable law or agreed to in writing, |
| 13 | + * software distributed under the License is distributed on an |
| 14 | + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 15 | + * KIND, either express or implied. See the License for the |
| 16 | + * specific language governing permissions and limitations |
| 17 | + * under the License. |
| 18 | + */ |
| 19 | +package org.elasticsearch.cluster.coordination; |
| 20 | + |
| 21 | +import joptsimple.OptionParser; |
| 22 | +import joptsimple.OptionSet; |
| 23 | +import joptsimple.OptionSpec; |
| 24 | +import org.apache.logging.log4j.LogManager; |
| 25 | +import org.apache.logging.log4j.Logger; |
| 26 | +import org.apache.lucene.store.LockObtainFailedException; |
| 27 | +import org.elasticsearch.ElasticsearchException; |
| 28 | +import org.elasticsearch.cli.EnvironmentAwareCommand; |
| 29 | +import org.elasticsearch.cli.Terminal; |
| 30 | +import org.elasticsearch.cluster.ClusterModule; |
| 31 | +import org.elasticsearch.cluster.metadata.Manifest; |
| 32 | +import org.elasticsearch.cluster.metadata.MetaData; |
| 33 | +import org.elasticsearch.common.collect.Tuple; |
| 34 | +import org.elasticsearch.common.xcontent.NamedXContentRegistry; |
| 35 | +import org.elasticsearch.env.Environment; |
| 36 | +import org.elasticsearch.env.NodeEnvironment; |
| 37 | + |
| 38 | +import java.io.IOException; |
| 39 | +import java.nio.file.Files; |
| 40 | +import java.nio.file.Path; |
| 41 | +import java.util.Arrays; |
| 42 | +import java.util.Objects; |
| 43 | + |
| 44 | +public abstract class ElasticsearchNodeCommand extends EnvironmentAwareCommand { |
| 45 | + private static final Logger logger = LogManager.getLogger(ElasticsearchNodeCommand.class); |
| 46 | + protected final NamedXContentRegistry namedXContentRegistry; |
| 47 | + static final String STOP_WARNING_MSG = |
| 48 | + "--------------------------------------------------------------------------\n" + |
| 49 | + "\n" + |
| 50 | + " WARNING: Elasticsearch MUST be stopped before running this tool." + |
| 51 | + "\n"; |
| 52 | + static final String FAILED_TO_OBTAIN_NODE_LOCK_MSG = "failed to lock node's directory, is Elasticsearch still running?"; |
| 53 | + static final String NO_NODE_FOLDER_FOUND_MSG = "no node folder is found in data folder(s), node has not been started yet?"; |
| 54 | + static final String NO_MANIFEST_FILE_FOUND_MSG = "no manifest file is found, do you run pre 7.0 Elasticsearch?"; |
| 55 | + static final String GLOBAL_GENERATION_MISSING_MSG = "no metadata is referenced from the manifest file, cluster has never been " + |
| 56 | + "bootstrapped?"; |
| 57 | + static final String NO_GLOBAL_METADATA_MSG = "failed to find global metadata, metadata corrupted?"; |
| 58 | + static final String WRITE_METADATA_EXCEPTION_MSG = "exception occurred when writing new metadata to disk"; |
| 59 | + static final String ABORTED_BY_USER_MSG = "aborted by user"; |
| 60 | + final OptionSpec<Integer> nodeOrdinalOption; |
| 61 | + |
| 62 | + public ElasticsearchNodeCommand(String description) { |
| 63 | + super(description); |
| 64 | + nodeOrdinalOption = parser.accepts("ordinal", "Optional node ordinal, 0 if not specified") |
| 65 | + .withRequiredArg().ofType(Integer.class); |
| 66 | + namedXContentRegistry = new NamedXContentRegistry(ClusterModule.getNamedXWriteables()); |
| 67 | + } |
| 68 | + |
| 69 | + protected void processNodePathsWithLock(Terminal terminal, OptionSet options, Environment env) throws IOException { |
| 70 | + terminal.println(Terminal.Verbosity.VERBOSE, "Obtaining lock for node"); |
| 71 | + Integer nodeOrdinal = nodeOrdinalOption.value(options); |
| 72 | + if (nodeOrdinal == null) { |
| 73 | + nodeOrdinal = 0; |
| 74 | + } |
| 75 | + try (NodeEnvironment.NodeLock lock = new NodeEnvironment.NodeLock(nodeOrdinal, logger, env, Files::exists)) { |
| 76 | + final Path[] dataPaths = |
| 77 | + Arrays.stream(lock.getNodePaths()).filter(Objects::nonNull).map(p -> p.path).toArray(Path[]::new); |
| 78 | + if (dataPaths.length == 0) { |
| 79 | + throw new ElasticsearchException(NO_NODE_FOLDER_FOUND_MSG); |
| 80 | + } |
| 81 | + processNodePaths(terminal, dataPaths); |
| 82 | + } catch (LockObtainFailedException ex) { |
| 83 | + throw new ElasticsearchException( |
| 84 | + FAILED_TO_OBTAIN_NODE_LOCK_MSG + " [" + ex.getMessage() + "]"); |
| 85 | + } |
| 86 | + } |
| 87 | + |
| 88 | + protected Tuple<Manifest, MetaData> loadMetaData(Terminal terminal, Path[] dataPaths) throws IOException { |
| 89 | + terminal.println(Terminal.Verbosity.VERBOSE, "Loading manifest file"); |
| 90 | + final Manifest manifest = Manifest.FORMAT.loadLatestState(logger, namedXContentRegistry, dataPaths); |
| 91 | + |
| 92 | + if (manifest == null) { |
| 93 | + throw new ElasticsearchException(NO_MANIFEST_FILE_FOUND_MSG); |
| 94 | + } |
| 95 | + if (manifest.isGlobalGenerationMissing()) { |
| 96 | + throw new ElasticsearchException(GLOBAL_GENERATION_MISSING_MSG); |
| 97 | + } |
| 98 | + terminal.println(Terminal.Verbosity.VERBOSE, "Loading global metadata file"); |
| 99 | + final MetaData metaData = MetaData.FORMAT.loadGeneration(logger, namedXContentRegistry, manifest.getGlobalGeneration(), |
| 100 | + dataPaths); |
| 101 | + if (metaData == null) { |
| 102 | + throw new ElasticsearchException(NO_GLOBAL_METADATA_MSG + " [generation = " + manifest.getGlobalGeneration() + "]"); |
| 103 | + } |
| 104 | + |
| 105 | + return Tuple.tuple(manifest, metaData); |
| 106 | + } |
| 107 | + |
| 108 | + protected void confirm(Terminal terminal, String msg) { |
| 109 | + terminal.println(msg); |
| 110 | + String text = terminal.readText("Confirm [y/N] "); |
| 111 | + if (text.equalsIgnoreCase("y") == false) { |
| 112 | + throw new ElasticsearchException(ABORTED_BY_USER_MSG); |
| 113 | + } |
| 114 | + } |
| 115 | + |
| 116 | + @Override |
| 117 | + protected void execute(Terminal terminal, OptionSet options, Environment env) throws Exception { |
| 118 | + terminal.println(STOP_WARNING_MSG); |
| 119 | + } |
| 120 | + |
| 121 | + protected abstract void processNodePaths(Terminal terminal, Path[] dataPaths) throws IOException; |
| 122 | + |
| 123 | + |
| 124 | + protected void writeNewMetaData(Terminal terminal, Manifest oldManifest, long newCurrentTerm, |
| 125 | + MetaData oldMetaData, MetaData newMetaData, Path[] dataPaths) { |
| 126 | + try { |
| 127 | + terminal.println(Terminal.Verbosity.VERBOSE, |
| 128 | + "[clusterUUID = " + oldMetaData.clusterUUID() + ", committed = " + oldMetaData.clusterUUIDCommitted() + "] => " + |
| 129 | + "[clusterUUID = " + newMetaData.clusterUUID() + ", committed = " + newMetaData.clusterUUIDCommitted() + "]"); |
| 130 | + terminal.println(Terminal.Verbosity.VERBOSE, "New coordination metadata is " + newMetaData.coordinationMetaData()); |
| 131 | + terminal.println(Terminal.Verbosity.VERBOSE, "Writing new global metadata to disk"); |
| 132 | + long newGeneration = MetaData.FORMAT.write(newMetaData, dataPaths); |
| 133 | + Manifest newManifest = new Manifest(newCurrentTerm, oldManifest.getClusterStateVersion(), newGeneration, |
| 134 | + oldManifest.getIndexGenerations()); |
| 135 | + terminal.println(Terminal.Verbosity.VERBOSE, "New manifest is " + newManifest); |
| 136 | + terminal.println(Terminal.Verbosity.VERBOSE, "Writing new manifest file to disk"); |
| 137 | + Manifest.FORMAT.writeAndCleanup(newManifest, dataPaths); |
| 138 | + terminal.println(Terminal.Verbosity.VERBOSE, "Cleaning up old metadata"); |
| 139 | + MetaData.FORMAT.cleanupOldFiles(newGeneration, dataPaths); |
| 140 | + } catch (Exception e) { |
| 141 | + terminal.println(Terminal.Verbosity.VERBOSE, "Cleaning up new metadata"); |
| 142 | + MetaData.FORMAT.cleanupOldFiles(oldManifest.getGlobalGeneration(), dataPaths); |
| 143 | + throw new ElasticsearchException(WRITE_METADATA_EXCEPTION_MSG, e); |
| 144 | + } |
| 145 | + } |
| 146 | + |
| 147 | + //package-private for testing |
| 148 | + OptionParser getParser() { |
| 149 | + return parser; |
| 150 | + } |
| 151 | +} |
0 commit comments