blob: f0774663b4be444284438909c12e1df3d0ac7151 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.accumulo.server.init;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ServerColumnFamily.DIRECTORY_COLUMN;
import static org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ServerColumnFamily.TIME_COLUMN;
import static org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.TabletColumnFamily.PREV_ROW_COLUMN;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;
import org.apache.accumulo.core.Constants;
import org.apache.accumulo.core.cli.Help;
import org.apache.accumulo.core.client.AccumuloSecurityException;
import org.apache.accumulo.core.client.IteratorSetting;
import org.apache.accumulo.core.client.IteratorSetting.Column;
import org.apache.accumulo.core.client.admin.TimeType;
import org.apache.accumulo.core.clientImpl.Namespace;
import org.apache.accumulo.core.conf.AccumuloConfiguration;
import org.apache.accumulo.core.conf.DefaultConfiguration;
import org.apache.accumulo.core.conf.Property;
import org.apache.accumulo.core.conf.SiteConfiguration;
import org.apache.accumulo.core.crypto.CryptoServiceFactory;
import org.apache.accumulo.core.crypto.CryptoServiceFactory.ClassloaderType;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.TableId;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.file.FileOperations;
import org.apache.accumulo.core.file.FileSKVWriter;
import org.apache.accumulo.core.iterators.Combiner;
import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
import org.apache.accumulo.core.iterators.user.VersioningIterator;
import org.apache.accumulo.core.manager.state.tables.TableState;
import org.apache.accumulo.core.master.thrift.MasterGoalState;
import org.apache.accumulo.core.metadata.MetadataTable;
import org.apache.accumulo.core.metadata.RootTable;
import org.apache.accumulo.core.metadata.schema.DataFileValue;
import org.apache.accumulo.core.metadata.schema.MetadataSchema.ReplicationSection;
import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection;
import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.CurrentLocationColumnFamily;
import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.DataFileColumnFamily;
import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.FutureLocationColumnFamily;
import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.LogColumnFamily;
import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ServerColumnFamily;
import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.TabletColumnFamily;
import org.apache.accumulo.core.metadata.schema.MetadataTime;
import org.apache.accumulo.core.metadata.schema.RootTabletMetadata;
import org.apache.accumulo.core.replication.ReplicationConstants;
import org.apache.accumulo.core.replication.ReplicationSchema.StatusSection;
import org.apache.accumulo.core.replication.ReplicationSchema.WorkSection;
import org.apache.accumulo.core.replication.ReplicationTable;
import org.apache.accumulo.core.singletons.SingletonManager;
import org.apache.accumulo.core.singletons.SingletonManager.Mode;
import org.apache.accumulo.core.spi.compaction.SimpleCompactionDispatcher;
import org.apache.accumulo.core.spi.crypto.CryptoService;
import org.apache.accumulo.core.spi.fs.VolumeChooserEnvironment;
import org.apache.accumulo.core.spi.fs.VolumeChooserEnvironment.Scope;
import org.apache.accumulo.core.util.ColumnFQ;
import org.apache.accumulo.core.util.LocalityGroupUtil;
import org.apache.accumulo.core.util.Pair;
import org.apache.accumulo.core.volume.VolumeConfiguration;
import org.apache.accumulo.fate.zookeeper.ZooReaderWriter;
import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeExistsPolicy;
import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeMissingPolicy;
import org.apache.accumulo.server.ServerConstants;
import org.apache.accumulo.server.ServerContext;
import org.apache.accumulo.server.ServerUtil;
import org.apache.accumulo.server.constraints.MetadataConstraints;
import org.apache.accumulo.server.fs.VolumeChooserEnvironmentImpl;
import org.apache.accumulo.server.fs.VolumeManager;
import org.apache.accumulo.server.fs.VolumeManagerImpl;
import org.apache.accumulo.server.iterators.MetadataBulkLoadFilter;
import org.apache.accumulo.server.log.WalStateManager;
import org.apache.accumulo.server.metadata.RootGcCandidates;
import org.apache.accumulo.server.replication.ReplicationUtil;
import org.apache.accumulo.server.replication.StatusCombiner;
import org.apache.accumulo.server.security.AuditedSecurityOperation;
import org.apache.accumulo.server.security.SecurityUtil;
import org.apache.accumulo.server.tables.TableManager;
import org.apache.accumulo.server.util.ReplicationTableUtil;
import org.apache.accumulo.server.util.SystemPropUtil;
import org.apache.accumulo.server.util.TablePropUtil;
import org.apache.accumulo.start.spi.KeywordExecutable;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs.Ids;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.utils.InfoCmp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.beust.jcommander.Parameter;
import com.google.auto.service.AutoService;
import com.google.common.base.Joiner;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* This class is used to setup the directory structure and the root tablet to get an instance
* started
*/
@SuppressFBWarnings(value = "DM_EXIT", justification = "CLI utility can exit")
@AutoService(KeywordExecutable.class)
public class Initialize implements KeywordExecutable {
private static final Logger log = LoggerFactory.getLogger(Initialize.class);
private static final String DEFAULT_ROOT_USER = "root";
private static final String TABLE_TABLETS_TABLET_DIR = "table_info";
private static LineReader reader = null;
private static ZooReaderWriter zoo = null;
private static LineReader getLineReader() throws IOException {
if (reader == null) {
reader = LineReaderBuilder.builder().build();
}
return reader;
}
/**
* Sets this class's ZooKeeper reader/writer.
*
* @param zooReaderWriter
* reader/writer
*/
static void setZooReaderWriter(ZooReaderWriter zooReaderWriter) {
zoo = zooReaderWriter;
}
/**
* Gets this class's ZooKeeper reader/writer.
*
* @return reader/writer
*/
static ZooReaderWriter getZooReaderWriter() {
return zoo;
}
// config only for root table
private static HashMap<String,String> initialRootConf = new HashMap<>();
// config for root and metadata table
private static HashMap<String,String> initialRootMetaConf = new HashMap<>();
// config for only metadata table
private static HashMap<String,String> initialMetaConf = new HashMap<>();
private static HashMap<String,String> initialReplicationTableConf = new HashMap<>();
static {
initialRootConf.put(Property.TABLE_COMPACTION_DISPATCHER.getKey(),
SimpleCompactionDispatcher.class.getName());
initialRootConf.put(Property.TABLE_COMPACTION_DISPATCHER_OPTS.getKey() + "service", "root");
initialRootMetaConf.put(Property.TABLE_FILE_COMPRESSED_BLOCK_SIZE.getKey(), "32K");
initialRootMetaConf.put(Property.TABLE_FILE_REPLICATION.getKey(), "5");
initialRootMetaConf.put(Property.TABLE_DURABILITY.getKey(), "sync");
initialRootMetaConf.put(Property.TABLE_MAJC_RATIO.getKey(), "1");
initialRootMetaConf.put(Property.TABLE_SPLIT_THRESHOLD.getKey(), "64M");
initialRootMetaConf.put(Property.TABLE_CONSTRAINT_PREFIX.getKey() + "1",
MetadataConstraints.class.getName());
initialRootMetaConf.put(Property.TABLE_ITERATOR_PREFIX.getKey() + "scan.vers",
"10," + VersioningIterator.class.getName());
initialRootMetaConf.put(Property.TABLE_ITERATOR_PREFIX.getKey() + "scan.vers.opt.maxVersions",
"1");
initialRootMetaConf.put(Property.TABLE_ITERATOR_PREFIX.getKey() + "minc.vers",
"10," + VersioningIterator.class.getName());
initialRootMetaConf.put(Property.TABLE_ITERATOR_PREFIX.getKey() + "minc.vers.opt.maxVersions",
"1");
initialRootMetaConf.put(Property.TABLE_ITERATOR_PREFIX.getKey() + "majc.vers",
"10," + VersioningIterator.class.getName());
initialRootMetaConf.put(Property.TABLE_ITERATOR_PREFIX.getKey() + "majc.vers.opt.maxVersions",
"1");
initialRootMetaConf.put(Property.TABLE_ITERATOR_PREFIX.getKey() + "majc.bulkLoadFilter",
"20," + MetadataBulkLoadFilter.class.getName());
initialRootMetaConf.put(Property.TABLE_FAILURES_IGNORE.getKey(), "false");
initialRootMetaConf.put(Property.TABLE_LOCALITY_GROUP_PREFIX.getKey() + "tablet",
String.format("%s,%s", TabletColumnFamily.NAME, CurrentLocationColumnFamily.NAME));
initialRootMetaConf.put(Property.TABLE_LOCALITY_GROUP_PREFIX.getKey() + "server",
String.format("%s,%s,%s,%s", DataFileColumnFamily.NAME, LogColumnFamily.NAME,
ServerColumnFamily.NAME, FutureLocationColumnFamily.NAME));
initialRootMetaConf.put(Property.TABLE_LOCALITY_GROUPS.getKey(), "tablet,server");
initialRootMetaConf.put(Property.TABLE_DEFAULT_SCANTIME_VISIBILITY.getKey(), "");
initialRootMetaConf.put(Property.TABLE_INDEXCACHE_ENABLED.getKey(), "true");
initialRootMetaConf.put(Property.TABLE_BLOCKCACHE_ENABLED.getKey(), "true");
initialMetaConf.put(Property.TABLE_COMPACTION_DISPATCHER.getKey(),
SimpleCompactionDispatcher.class.getName());
initialMetaConf.put(Property.TABLE_COMPACTION_DISPATCHER_OPTS.getKey() + "service", "meta");
// ACCUMULO-3077 Set the combiner on accumulo.metadata during init to reduce the likelihood of a
// race condition where a tserver compacts away Status updates because it didn't see the
// Combiner
// configured
IteratorSetting setting =
new IteratorSetting(9, ReplicationTableUtil.COMBINER_NAME, StatusCombiner.class);
Combiner.setColumns(setting, Collections.singletonList(new Column(ReplicationSection.COLF)));
for (IteratorScope scope : IteratorScope.values()) {
String root = String.format("%s%s.%s", Property.TABLE_ITERATOR_PREFIX,
scope.name().toLowerCase(), setting.getName());
for (Entry<String,String> prop : setting.getOptions().entrySet()) {
initialMetaConf.put(root + ".opt." + prop.getKey(), prop.getValue());
}
initialMetaConf.put(root, setting.getPriority() + "," + setting.getIteratorClass());
}
// add combiners to replication table
setting = new IteratorSetting(30, ReplicationTable.COMBINER_NAME, StatusCombiner.class);
setting.setPriority(30);
Combiner.setColumns(setting,
Arrays.asList(new Column(StatusSection.NAME), new Column(WorkSection.NAME)));
for (IteratorScope scope : EnumSet.allOf(IteratorScope.class)) {
String root = String.format("%s%s.%s", Property.TABLE_ITERATOR_PREFIX,
scope.name().toLowerCase(), setting.getName());
for (Entry<String,String> prop : setting.getOptions().entrySet()) {
initialReplicationTableConf.put(root + ".opt." + prop.getKey(), prop.getValue());
}
initialReplicationTableConf.put(root,
setting.getPriority() + "," + setting.getIteratorClass());
}
// add locality groups to replication table
for (Entry<String,Set<Text>> g : ReplicationTable.LOCALITY_GROUPS.entrySet()) {
initialReplicationTableConf.put(Property.TABLE_LOCALITY_GROUP_PREFIX + g.getKey(),
LocalityGroupUtil.encodeColumnFamilies(g.getValue()));
}
initialReplicationTableConf.put(Property.TABLE_LOCALITY_GROUPS.getKey(),
Joiner.on(",").join(ReplicationTable.LOCALITY_GROUPS.keySet()));
// add formatter to replication table
initialReplicationTableConf.put(Property.TABLE_FORMATTER_CLASS.getKey(),
ReplicationUtil.STATUS_FORMATTER_CLASS_NAME);
}
static boolean checkInit(VolumeManager fs, SiteConfiguration sconf, Configuration hadoopConf)
throws IOException {
log.info("Hadoop Filesystem is {}", FileSystem.getDefaultUri(hadoopConf));
log.info("Accumulo data dirs are {}", Arrays.asList(VolumeConfiguration.getVolumeUris(sconf)));
log.info("Zookeeper server is {}", sconf.get(Property.INSTANCE_ZK_HOST));
log.info("Checking if Zookeeper is available. If this hangs, then you need"
+ " to make sure zookeeper is running");
if (!zookeeperAvailable()) {
// ACCUMULO-3651 Changed level to error and added FATAL to message for slf4j compatibility
log.error("FATAL Zookeeper needs to be up and running in order to init. Exiting ...");
return false;
}
if (sconf.get(Property.INSTANCE_SECRET).equals(Property.INSTANCE_SECRET.getDefaultValue())) {
LineReader c = getLineReader();
var w = c.getTerminal().writer();
c.getTerminal().puts(InfoCmp.Capability.bell);
w.println();
w.println();
w.println("Warning!!! Your instance secret is still set to the default,"
+ " this is not secure. We highly recommend you change it.");
w.println();
w.println();
w.println("You can change the instance secret in accumulo by using:");
w.println(" bin/accumulo " + org.apache.accumulo.server.util.ChangeSecret.class.getName());
w.println("You will also need to edit your secret in your configuration"
+ " file by adding the property instance.secret to your"
+ " accumulo.properties. Without this accumulo will not operate" + " correctly");
}
try {
if (isInitialized(fs, sconf)) {
printInitializeFailureMessages(sconf);
return false;
}
} catch (IOException e) {
throw new IOException("Failed to check if filesystem already initialized", e);
}
return true;
}
static void printInitializeFailureMessages(SiteConfiguration sconf) {
log.error("It appears the directories {}",
VolumeConfiguration.getVolumeUris(sconf) + " were previously initialized.");
log.error("Change the property {} to use different volumes.",
Property.INSTANCE_VOLUMES.getKey());
log.error("The current value of {} is |{}|", Property.INSTANCE_VOLUMES.getKey(),
sconf.get(Property.INSTANCE_VOLUMES));
}
public boolean doInit(SiteConfiguration siteConfig, Opts opts, Configuration hadoopConf,
VolumeManager fs) throws IOException {
if (!checkInit(fs, siteConfig, hadoopConf)) {
return false;
}
// prompt user for instance name and root password early, in case they
// abort, we don't leave an inconsistent HDFS/ZooKeeper structure
String instanceNamePath;
try {
instanceNamePath = getInstanceNamePath(opts);
} catch (Exception e) {
log.error("FATAL: Failed to talk to zookeeper", e);
return false;
}
String rootUser;
try {
rootUser = getRootUserName(siteConfig, opts);
} catch (Exception e) {
log.error("FATAL: Failed to obtain user for administrative privileges");
return false;
}
// Don't prompt for a password when we're running SASL(Kerberos)
if (siteConfig.getBoolean(Property.INSTANCE_RPC_SASL_ENABLED)) {
opts.rootpass = UUID.randomUUID().toString().getBytes(UTF_8);
} else {
opts.rootpass = getRootPassword(siteConfig, opts, rootUser);
}
UUID uuid = UUID.randomUUID();
// the actual disk locations of the root table and tablets
Set<String> configuredVolumes = VolumeConfiguration.getVolumeUris(siteConfig);
String instanceName = instanceNamePath.substring(getInstanceNamePrefix().length());
try (ServerContext context =
ServerContext.initialize(siteConfig, instanceName, uuid.toString())) {
VolumeChooserEnvironment chooserEnv =
new VolumeChooserEnvironmentImpl(Scope.INIT, RootTable.ID, null, context);
String rootTabletDirName = RootTable.ROOT_TABLET_DIR_NAME;
String ext = FileOperations.getNewFileExtension(DefaultConfiguration.getInstance());
String rootTabletFileUri = new Path(fs.choose(chooserEnv, configuredVolumes) + Path.SEPARATOR
+ ServerConstants.TABLE_DIR + Path.SEPARATOR + RootTable.ID + Path.SEPARATOR
+ rootTabletDirName + Path.SEPARATOR + "00000_00000." + ext).toString();
try {
initZooKeeper(opts, uuid.toString(), instanceNamePath, rootTabletDirName,
rootTabletFileUri);
} catch (Exception e) {
log.error("FATAL: Failed to initialize zookeeper", e);
return false;
}
try {
initFileSystem(siteConfig, hadoopConf, fs, uuid,
new Path(fs.choose(chooserEnv, configuredVolumes) + Path.SEPARATOR
+ ServerConstants.TABLE_DIR + Path.SEPARATOR + RootTable.ID + rootTabletDirName)
.toString(),
rootTabletFileUri, context);
} catch (Exception e) {
log.error("FATAL Failed to initialize filesystem", e);
return false;
}
// When we're using Kerberos authentication, we need valid credentials to perform
// initialization. If the user provided some, use them.
// If they did not, fall back to the credentials present in accumulo.properties that the
// servers will use themselves.
try {
if (siteConfig.getBoolean(Property.INSTANCE_RPC_SASL_ENABLED)) {
final UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
// We don't have any valid creds to talk to HDFS
if (!ugi.hasKerberosCredentials()) {
final String accumuloKeytab = siteConfig.get(Property.GENERAL_KERBEROS_KEYTAB),
accumuloPrincipal = siteConfig.get(Property.GENERAL_KERBEROS_PRINCIPAL);
// Fail if the site configuration doesn't contain appropriate credentials to login as
// servers
if (StringUtils.isBlank(accumuloKeytab) || StringUtils.isBlank(accumuloPrincipal)) {
log.error("FATAL: No Kerberos credentials provided, and Accumulo is"
+ " not properly configured for server login");
return false;
}
log.info("Logging in as {} with {}", accumuloPrincipal, accumuloKeytab);
// Login using the keytab as the 'accumulo' user
UserGroupInformation.loginUserFromKeytab(accumuloPrincipal, accumuloKeytab);
}
}
} catch (IOException e) {
log.error("FATAL: Failed to get the Kerberos user", e);
return false;
}
try {
initSecurity(context, opts, rootUser);
} catch (Exception e) {
log.error("FATAL: Failed to initialize security", e);
return false;
}
if (opts.uploadAccumuloProps) {
try {
log.info("Uploading properties in accumulo.properties to Zookeeper."
+ " Properties that cannot be set in Zookeeper will be skipped:");
Map<String,String> entries = new TreeMap<>();
siteConfig.getProperties(entries, x -> true, false);
for (Map.Entry<String,String> entry : entries.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (Property.isValidZooPropertyKey(key)) {
SystemPropUtil.setSystemProperty(context, key, value);
log.info("Uploaded - {} = {}", key, Property.isSensitive(key) ? "<hidden>" : value);
} else {
log.info("Skipped - {} = {}", key, Property.isSensitive(key) ? "<hidden>" : value);
}
}
} catch (Exception e) {
log.error("FATAL: Failed to upload accumulo.properties to Zookeeper", e);
return false;
}
}
return true;
}
}
private static boolean zookeeperAvailable() {
try {
return zoo.exists("/");
} catch (KeeperException | InterruptedException e) {
return false;
}
}
private static void initDirs(VolumeManager fs, UUID uuid, Set<String> baseDirs, boolean print)
throws IOException {
for (String baseDir : baseDirs) {
fs.mkdirs(new Path(new Path(baseDir, ServerConstants.VERSION_DIR),
"" + ServerConstants.DATA_VERSION), new FsPermission("700"));
// create an instance id
Path iidLocation = new Path(baseDir, ServerConstants.INSTANCE_ID_DIR);
fs.mkdirs(iidLocation);
fs.createNewFile(new Path(iidLocation, uuid.toString()));
if (print) {
log.info("Initialized volume {}", baseDir);
}
}
}
private void initFileSystem(SiteConfiguration siteConfig, Configuration hadoopConf,
VolumeManager fs, UUID uuid, String rootTabletDirUri, String rootTabletFileUri,
ServerContext serverContext) throws IOException {
initDirs(fs, uuid, VolumeConfiguration.getVolumeUris(siteConfig), false);
// initialize initial system tables config in zookeeper
initSystemTablesConfig(zoo, Constants.ZROOT + "/" + uuid, hadoopConf);
Text splitPoint = TabletsSection.getRange().getEndKey().getRow();
VolumeChooserEnvironment chooserEnv =
new VolumeChooserEnvironmentImpl(Scope.INIT, MetadataTable.ID, splitPoint, serverContext);
String tableMetadataTabletDirName = TABLE_TABLETS_TABLET_DIR;
String tableMetadataTabletDirUri =
fs.choose(chooserEnv, ServerConstants.getBaseUris(siteConfig, hadoopConf))
+ Constants.HDFS_TABLES_DIR + Path.SEPARATOR + MetadataTable.ID + Path.SEPARATOR
+ tableMetadataTabletDirName;
chooserEnv =
new VolumeChooserEnvironmentImpl(Scope.INIT, ReplicationTable.ID, null, serverContext);
String replicationTableDefaultTabletDirName = ServerColumnFamily.DEFAULT_TABLET_DIR_NAME;
String replicationTableDefaultTabletDirUri =
fs.choose(chooserEnv, ServerConstants.getBaseUris(siteConfig, hadoopConf))
+ Constants.HDFS_TABLES_DIR + Path.SEPARATOR + ReplicationTable.ID + Path.SEPARATOR
+ replicationTableDefaultTabletDirName;
chooserEnv =
new VolumeChooserEnvironmentImpl(Scope.INIT, MetadataTable.ID, null, serverContext);
String defaultMetadataTabletDirName = ServerColumnFamily.DEFAULT_TABLET_DIR_NAME;
String defaultMetadataTabletDirUri =
fs.choose(chooserEnv, ServerConstants.getBaseUris(siteConfig, hadoopConf))
+ Constants.HDFS_TABLES_DIR + Path.SEPARATOR + MetadataTable.ID + Path.SEPARATOR
+ defaultMetadataTabletDirName;
// create table and default tablets directories
createDirectories(fs, rootTabletDirUri, tableMetadataTabletDirUri, defaultMetadataTabletDirUri,
replicationTableDefaultTabletDirUri);
String ext = FileOperations.getNewFileExtension(DefaultConfiguration.getInstance());
// populate the metadata tables tablet with info about the replication table's one initial
// tablet
String metadataFileName = tableMetadataTabletDirUri + Path.SEPARATOR + "0_1." + ext;
Tablet replicationTablet =
new Tablet(ReplicationTable.ID, replicationTableDefaultTabletDirName, null, null);
createMetadataFile(fs, metadataFileName, siteConfig, replicationTablet);
// populate the root tablet with info about the metadata table's two initial tablets
Tablet tablesTablet = new Tablet(MetadataTable.ID, tableMetadataTabletDirName, null, splitPoint,
metadataFileName);
Tablet defaultTablet =
new Tablet(MetadataTable.ID, defaultMetadataTabletDirName, splitPoint, null);
createMetadataFile(fs, rootTabletFileUri, siteConfig, tablesTablet, defaultTablet);
}
private static class Tablet {
TableId tableId;
String dirName;
Text prevEndRow, endRow;
String[] files;
Tablet(TableId tableId, String dirName, Text prevEndRow, Text endRow, String... files) {
this.tableId = tableId;
this.dirName = dirName;
this.prevEndRow = prevEndRow;
this.endRow = endRow;
this.files = files;
}
}
private static void createMetadataFile(VolumeManager volmanager, String fileName,
AccumuloConfiguration conf, Tablet... tablets) throws IOException {
// sort file contents in memory, then play back to the file
TreeMap<Key,Value> sorted = new TreeMap<>();
for (Tablet tablet : tablets) {
createEntriesForTablet(sorted, tablet);
}
FileSystem fs = volmanager.getFileSystemByPath(new Path(fileName));
CryptoService cs = CryptoServiceFactory.newInstance(conf, ClassloaderType.ACCUMULO);
FileSKVWriter tabletWriter = FileOperations.getInstance().newWriterBuilder()
.forFile(fileName, fs, fs.getConf(), cs).withTableConfiguration(conf).build();
tabletWriter.startDefaultLocalityGroup();
for (Entry<Key,Value> entry : sorted.entrySet()) {
tabletWriter.append(entry.getKey(), entry.getValue());
}
tabletWriter.close();
}
private static void createEntriesForTablet(TreeMap<Key,Value> map, Tablet tablet) {
Value EMPTY_SIZE = new DataFileValue(0, 0).encodeAsValue();
Text extent = new Text(TabletsSection.encodeRow(tablet.tableId, tablet.endRow));
addEntry(map, extent, DIRECTORY_COLUMN, new Value(tablet.dirName));
addEntry(map, extent, TIME_COLUMN, new Value(new MetadataTime(0, TimeType.LOGICAL).encode()));
addEntry(map, extent, PREV_ROW_COLUMN, TabletColumnFamily.encodePrevEndRow(tablet.prevEndRow));
for (String file : tablet.files) {
addEntry(map, extent, new ColumnFQ(DataFileColumnFamily.NAME, new Text(file)), EMPTY_SIZE);
}
}
private static void addEntry(TreeMap<Key,Value> map, Text row, ColumnFQ col, Value value) {
map.put(new Key(row, col.getColumnFamily(), col.getColumnQualifier(), 0), value);
}
private static void createDirectories(VolumeManager fs, String... dirs) throws IOException {
for (String s : dirs) {
Path dir = new Path(s);
try {
FileStatus fstat = fs.getFileStatus(dir);
if (!fstat.isDirectory()) {
log.error("FATAL: location {} exists but is not a directory", dir);
return;
}
} catch (FileNotFoundException fnfe) {
// attempt to create directory, since it doesn't exist
if (!fs.mkdirs(dir)) {
log.error("FATAL: unable to create directory {}", dir);
return;
}
}
}
}
private static void initZooKeeper(Opts opts, String uuid, String instanceNamePath,
String rootTabletDirName, String rootTabletFileUri)
throws KeeperException, InterruptedException {
// setup basic data in zookeeper
zoo.putPersistentData(Constants.ZROOT, new byte[0], NodeExistsPolicy.SKIP, Ids.OPEN_ACL_UNSAFE);
zoo.putPersistentData(Constants.ZROOT + Constants.ZINSTANCES, new byte[0],
NodeExistsPolicy.SKIP, Ids.OPEN_ACL_UNSAFE);
// setup instance name
if (opts.clearInstanceName) {
zoo.recursiveDelete(instanceNamePath, NodeMissingPolicy.SKIP);
}
zoo.putPersistentData(instanceNamePath, uuid.getBytes(UTF_8), NodeExistsPolicy.FAIL);
final byte[] EMPTY_BYTE_ARRAY = new byte[0];
final byte[] ZERO_CHAR_ARRAY = {'0'};
// setup the instance
String zkInstanceRoot = Constants.ZROOT + "/" + uuid;
zoo.putPersistentData(zkInstanceRoot, EMPTY_BYTE_ARRAY, NodeExistsPolicy.FAIL);
zoo.putPersistentData(zkInstanceRoot + Constants.ZTABLES, Constants.ZTABLES_INITIAL_ID,
NodeExistsPolicy.FAIL);
zoo.putPersistentData(zkInstanceRoot + Constants.ZNAMESPACES, new byte[0],
NodeExistsPolicy.FAIL);
TableManager.prepareNewNamespaceState(zoo, uuid, Namespace.DEFAULT.id(),
Namespace.DEFAULT.name(), NodeExistsPolicy.FAIL);
TableManager.prepareNewNamespaceState(zoo, uuid, Namespace.ACCUMULO.id(),
Namespace.ACCUMULO.name(), NodeExistsPolicy.FAIL);
TableManager.prepareNewTableState(zoo, uuid, RootTable.ID, Namespace.ACCUMULO.id(),
RootTable.NAME, TableState.ONLINE, NodeExistsPolicy.FAIL);
TableManager.prepareNewTableState(zoo, uuid, MetadataTable.ID, Namespace.ACCUMULO.id(),
MetadataTable.NAME, TableState.ONLINE, NodeExistsPolicy.FAIL);
TableManager.prepareNewTableState(zoo, uuid, ReplicationTable.ID, Namespace.ACCUMULO.id(),
ReplicationTable.NAME, TableState.OFFLINE, NodeExistsPolicy.FAIL);
zoo.putPersistentData(zkInstanceRoot + Constants.ZTSERVERS, EMPTY_BYTE_ARRAY,
NodeExistsPolicy.FAIL);
zoo.putPersistentData(zkInstanceRoot + Constants.ZPROBLEMS, EMPTY_BYTE_ARRAY,
NodeExistsPolicy.FAIL);
zoo.putPersistentData(zkInstanceRoot + RootTable.ZROOT_TABLET,
RootTabletMetadata.getInitialJson(rootTabletDirName, rootTabletFileUri),
NodeExistsPolicy.FAIL);
zoo.putPersistentData(zkInstanceRoot + RootTable.ZROOT_TABLET_GC_CANDIDATES,
new RootGcCandidates().toJson().getBytes(UTF_8), NodeExistsPolicy.FAIL);
zoo.putPersistentData(zkInstanceRoot + Constants.ZMASTERS, EMPTY_BYTE_ARRAY,
NodeExistsPolicy.FAIL);
zoo.putPersistentData(zkInstanceRoot + Constants.ZMASTER_LOCK, EMPTY_BYTE_ARRAY,
NodeExistsPolicy.FAIL);
zoo.putPersistentData(zkInstanceRoot + Constants.ZMASTER_GOAL_STATE,
MasterGoalState.NORMAL.toString().getBytes(UTF_8), NodeExistsPolicy.FAIL);
zoo.putPersistentData(zkInstanceRoot + Constants.ZGC, EMPTY_BYTE_ARRAY, NodeExistsPolicy.FAIL);
zoo.putPersistentData(zkInstanceRoot + Constants.ZGC_LOCK, EMPTY_BYTE_ARRAY,
NodeExistsPolicy.FAIL);
zoo.putPersistentData(zkInstanceRoot + Constants.ZCONFIG, EMPTY_BYTE_ARRAY,
NodeExistsPolicy.FAIL);
zoo.putPersistentData(zkInstanceRoot + Constants.ZTABLE_LOCKS, EMPTY_BYTE_ARRAY,
NodeExistsPolicy.FAIL);
zoo.putPersistentData(zkInstanceRoot + Constants.ZHDFS_RESERVATIONS, EMPTY_BYTE_ARRAY,
NodeExistsPolicy.FAIL);
zoo.putPersistentData(zkInstanceRoot + Constants.ZNEXT_FILE, ZERO_CHAR_ARRAY,
NodeExistsPolicy.FAIL);
zoo.putPersistentData(zkInstanceRoot + Constants.ZRECOVERY, ZERO_CHAR_ARRAY,
NodeExistsPolicy.FAIL);
zoo.putPersistentData(zkInstanceRoot + Constants.ZMONITOR, EMPTY_BYTE_ARRAY,
NodeExistsPolicy.FAIL);
zoo.putPersistentData(zkInstanceRoot + Constants.ZMONITOR_LOCK, EMPTY_BYTE_ARRAY,
NodeExistsPolicy.FAIL);
zoo.putPersistentData(zkInstanceRoot + ReplicationConstants.ZOO_BASE, EMPTY_BYTE_ARRAY,
NodeExistsPolicy.FAIL);
zoo.putPersistentData(zkInstanceRoot + ReplicationConstants.ZOO_TSERVERS, EMPTY_BYTE_ARRAY,
NodeExistsPolicy.FAIL);
zoo.putPersistentData(zkInstanceRoot + WalStateManager.ZWALS, EMPTY_BYTE_ARRAY,
NodeExistsPolicy.FAIL);
}
private String getInstanceNamePrefix() {
return Constants.ZROOT + Constants.ZINSTANCES + "/";
}
private String getInstanceNamePath(Opts opts)
throws IOException, KeeperException, InterruptedException {
// setup the instance name
String instanceName, instanceNamePath = null;
boolean exists = true;
do {
if (opts.cliInstanceName == null) {
instanceName = getLineReader().readLine("Instance name : ");
} else {
instanceName = opts.cliInstanceName;
}
if (instanceName == null) {
System.exit(0);
}
instanceName = instanceName.trim();
if (instanceName.isEmpty()) {
continue;
}
instanceNamePath = getInstanceNamePrefix() + instanceName;
if (opts.clearInstanceName) {
exists = false;
} else {
// ACCUMULO-4401 setting exists=false is just as important as setting it to true
exists = zoo.exists(instanceNamePath);
if (exists) {
String decision = getLineReader().readLine("Instance name \"" + instanceName
+ "\" exists. Delete existing entry from zookeeper? [Y/N] : ");
if (decision == null) {
System.exit(0);
}
if (decision.length() == 1 && decision.toLowerCase(Locale.ENGLISH).charAt(0) == 'y') {
opts.clearInstanceName = true;
exists = false;
}
}
}
} while (exists);
return instanceNamePath;
}
private String getRootUserName(SiteConfiguration siteConfig, Opts opts) throws IOException {
final String keytab = siteConfig.get(Property.GENERAL_KERBEROS_KEYTAB);
if (keytab.equals(Property.GENERAL_KERBEROS_KEYTAB.getDefaultValue())
|| !siteConfig.getBoolean(Property.INSTANCE_RPC_SASL_ENABLED)) {
return DEFAULT_ROOT_USER;
}
LineReader c = getLineReader();
c.getTerminal().writer().println("Running against secured HDFS");
if (opts.rootUser != null) {
return opts.rootUser;
}
do {
String user = c.readLine("Principal (user) to grant administrative privileges to : ");
if (user == null) {
// should not happen
System.exit(1);
}
if (!user.isEmpty()) {
return user;
}
} while (true);
}
private byte[] getRootPassword(SiteConfiguration siteConfig, Opts opts, String rootUser)
throws IOException {
if (opts.cliPassword != null) {
return opts.cliPassword.getBytes(UTF_8);
}
String rootpass;
String confirmpass;
do {
rootpass = getLineReader().readLine(
"Enter initial password for " + rootUser + getInitialPasswordWarning(siteConfig), '*');
if (rootpass == null) {
System.exit(0);
}
confirmpass =
getLineReader().readLine("Confirm initial password for " + rootUser + ": ", '*');
if (confirmpass == null) {
System.exit(0);
}
if (!rootpass.equals(confirmpass)) {
log.error("Passwords do not match");
}
} while (!rootpass.equals(confirmpass));
return rootpass.getBytes(UTF_8);
}
/**
* Create warning message related to initial password, if appropriate.
*
* ACCUMULO-2907 Remove unnecessary security warning from console message unless its actually
* appropriate. The warning message should only be displayed when the value of
* <code>instance.security.authenticator</code> differs between the SiteConfiguration and the
* DefaultConfiguration values.
*
* @return String containing warning portion of console message.
*/
private String getInitialPasswordWarning(SiteConfiguration siteConfig) {
String optionalWarning;
Property authenticatorProperty = Property.INSTANCE_SECURITY_AUTHENTICATOR;
if (siteConfig.get(authenticatorProperty).equals(authenticatorProperty.getDefaultValue())) {
optionalWarning = ": ";
} else {
optionalWarning = " (this may not be applicable for your security setup): ";
}
return optionalWarning;
}
private static void initSecurity(ServerContext context, Opts opts, String rootUser)
throws AccumuloSecurityException {
AuditedSecurityOperation.getInstance(context).initializeSecurity(context.rpcCreds(), rootUser,
opts.rootpass);
}
public static void initSystemTablesConfig(ZooReaderWriter zoo, String zooKeeperRoot,
Configuration hadoopConf) throws IOException {
try {
int max = hadoopConf.getInt("dfs.replication.max", 512);
// Hadoop 0.23 switched the min value configuration name
int min = Math.max(hadoopConf.getInt("dfs.replication.min", 1),
hadoopConf.getInt("dfs.namenode.replication.min", 1));
if (max < 5) {
setMetadataReplication(max, "max");
}
if (min > 5) {
setMetadataReplication(min, "min");
}
for (Entry<String,String> entry : initialRootConf.entrySet()) {
if (!TablePropUtil.setTableProperty(zoo, zooKeeperRoot, RootTable.ID, entry.getKey(),
entry.getValue())) {
throw new IOException("Cannot create per-table property " + entry.getKey());
}
}
for (Entry<String,String> entry : initialRootMetaConf.entrySet()) {
if (!TablePropUtil.setTableProperty(zoo, zooKeeperRoot, RootTable.ID, entry.getKey(),
entry.getValue())) {
throw new IOException("Cannot create per-table property " + entry.getKey());
}
if (!TablePropUtil.setTableProperty(zoo, zooKeeperRoot, MetadataTable.ID, entry.getKey(),
entry.getValue())) {
throw new IOException("Cannot create per-table property " + entry.getKey());
}
}
for (Entry<String,String> entry : initialMetaConf.entrySet()) {
if (!TablePropUtil.setTableProperty(zoo, zooKeeperRoot, MetadataTable.ID, entry.getKey(),
entry.getValue())) {
throw new IOException("Cannot create per-table property " + entry.getKey());
}
}
// add configuration to the replication table
for (Entry<String,String> entry : initialReplicationTableConf.entrySet()) {
if (!TablePropUtil.setTableProperty(zoo, zooKeeperRoot, ReplicationTable.ID, entry.getKey(),
entry.getValue())) {
throw new IOException("Cannot create per-table property " + entry.getKey());
}
}
} catch (Exception e) {
log.error("FATAL: Error talking to ZooKeeper", e);
throw new IOException(e);
}
}
private static void setMetadataReplication(int replication, String reason) throws IOException {
String rep = getLineReader()
.readLine("Your HDFS replication " + reason + " is not compatible with our default "
+ MetadataTable.NAME + " replication of 5. What do you want to set your "
+ MetadataTable.NAME + " replication to? (" + replication + ") ");
if (rep == null || rep.isEmpty()) {
rep = Integer.toString(replication);
} else {
// Lets make sure it's a number
Integer.parseInt(rep);
}
initialRootMetaConf.put(Property.TABLE_FILE_REPLICATION.getKey(), rep);
}
public static boolean isInitialized(VolumeManager fs, SiteConfiguration siteConfig)
throws IOException {
for (String baseDir : VolumeConfiguration.getVolumeUris(siteConfig)) {
if (fs.exists(new Path(baseDir, ServerConstants.INSTANCE_ID_DIR))
|| fs.exists(new Path(baseDir, ServerConstants.VERSION_DIR))) {
return true;
}
}
return false;
}
private static void addVolumes(VolumeManager fs, SiteConfiguration siteConfig,
Configuration hadoopConf) throws IOException {
Set<String> volumeURIs = VolumeConfiguration.getVolumeUris(siteConfig);
Set<String> initializedDirs =
ServerConstants.checkBaseUris(siteConfig, hadoopConf, volumeURIs, true);
HashSet<String> uinitializedDirs = new HashSet<>();
uinitializedDirs.addAll(volumeURIs);
uinitializedDirs.removeAll(initializedDirs);
Path aBasePath = new Path(initializedDirs.iterator().next());
Path iidPath = new Path(aBasePath, ServerConstants.INSTANCE_ID_DIR);
Path versionPath = new Path(aBasePath, ServerConstants.VERSION_DIR);
UUID uuid = UUID.fromString(VolumeManager.getInstanceIDFromHdfs(iidPath, hadoopConf));
for (Pair<Path,Path> replacementVolume : ServerConstants.getVolumeReplacements(siteConfig,
hadoopConf)) {
if (aBasePath.equals(replacementVolume.getFirst())) {
log.error(
"{} is set to be replaced in {} and should not appear in {}."
+ " It is highly recommended that this property be removed as data"
+ " could still be written to this volume.",
aBasePath, Property.INSTANCE_VOLUMES_REPLACEMENTS, Property.INSTANCE_VOLUMES);
}
}
if (ServerUtil.getAccumuloPersistentVersion(versionPath.getFileSystem(hadoopConf), versionPath)
!= ServerConstants.DATA_VERSION) {
throw new IOException("Accumulo " + Constants.VERSION + " cannot initialize data version "
+ ServerUtil.getAccumuloPersistentVersion(fs));
}
initDirs(fs, uuid, uinitializedDirs, true);
}
static class Opts extends Help {
@Parameter(names = "--add-volumes",
description = "Initialize any uninitialized volumes listed in instance.volumes")
boolean addVolumes = false;
@Parameter(names = "--reset-security",
description = "just update the security information, will prompt")
boolean resetSecurity = false;
@Parameter(names = {"-f", "--force"},
description = "force reset of the security information without prompting")
boolean forceResetSecurity = false;
@Parameter(names = "--clear-instance-name",
description = "delete any existing instance name without prompting")
boolean clearInstanceName = false;
@Parameter(names = "--upload-accumulo-props",
description = "Uploads properties in accumulo.properties to Zookeeper")
boolean uploadAccumuloProps = false;
@Parameter(names = "--instance-name",
description = "the instance name, if not provided, will prompt")
String cliInstanceName = null;
@Parameter(names = "--password", description = "set the password on the command line")
String cliPassword = null;
@Parameter(names = {"-u", "--user"},
description = "the name of the user to grant system permissions to")
String rootUser = null;
byte[] rootpass = null;
}
@Override
public String keyword() {
return "init";
}
@Override
public UsageGroup usageGroup() {
return UsageGroup.CORE;
}
@Override
public String description() {
return "Initializes Accumulo";
}
@Override
public void execute(final String[] args) {
Opts opts = new Opts();
opts.parseArgs("accumulo init", args);
var siteConfig = SiteConfiguration.auto();
try {
setZooReaderWriter(new ZooReaderWriter(siteConfig));
SecurityUtil.serverLogin(siteConfig);
Configuration hadoopConfig = new Configuration();
try (var fs = VolumeManagerImpl.get(siteConfig, hadoopConfig)) {
if (opts.resetSecurity) {
log.info("Resetting security on accumulo.");
try (ServerContext context = new ServerContext(siteConfig)) {
if (isInitialized(fs, siteConfig)) {
if (!opts.forceResetSecurity) {
LineReader c = getLineReader();
String userEnteredName = c.readLine("WARNING: This will remove all"
+ " users from Accumulo! If you wish to proceed enter the instance"
+ " name: ");
if (userEnteredName != null && !context.getInstanceName().equals(userEnteredName)) {
log.error(
"Aborted reset security: Instance name did not match current instance.");
return;
}
}
final String rootUser = getRootUserName(siteConfig, opts);
opts.rootpass = getRootPassword(siteConfig, opts, rootUser);
initSecurity(context, opts, rootUser);
} else {
log.error("FATAL: Attempted to reset security on accumulo before it was initialized");
}
}
}
if (opts.addVolumes) {
addVolumes(fs, siteConfig, hadoopConfig);
}
if (!opts.resetSecurity && !opts.addVolumes) {
if (!doInit(siteConfig, opts, hadoopConfig, fs)) {
System.exit(-1);
}
}
}
} catch (Exception e) {
log.error("Fatal exception", e);
throw new RuntimeException(e);
} finally {
SingletonManager.setMode(Mode.CLOSED);
}
}
public static void main(String[] args) {
new Initialize().execute(args);
}
}