blob: 4aca4936f4653391914e88117299fc3a305cc8c8 [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.fs;
import static org.apache.commons.lang.ArrayUtils.EMPTY_STRING_ARRAY;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import org.apache.accumulo.core.conf.Property;
import org.apache.accumulo.core.volume.Volume;
import org.apache.accumulo.server.client.HdfsZooInstance;
import org.apache.accumulo.server.conf.ServerConfigurationFactory;
import org.apache.accumulo.server.conf.TableConfiguration;
import org.apache.commons.collections.map.LRUMap;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A {@link RandomVolumeChooser} that limits its choices from a given set of options to the subset of those options preferred for a particular table. Defaults
* to selecting from all of the options presented. Can be customized via the table property {@value #PREFERRED_VOLUMES_CUSTOM_KEY}, which should contain a comma
* separated list of {@link Volume} URIs. Note that both the property name and the format of its value are specific to this particular implementation.
*/
public class PreferredVolumeChooser extends RandomVolumeChooser {
private static final Logger log = LoggerFactory.getLogger(PreferredVolumeChooser.class);
/**
* This should match {@link Property#TABLE_ARBITRARY_PROP_PREFIX}
*/
public static final String PREFERRED_VOLUMES_CUSTOM_KEY = "table.custom.preferredVolumes";
// TODO ACCUMULO-3417 replace this with the ability to retrieve by String key.
private static final Predicate<String> PREFERRED_VOLUMES_FILTER = key -> PREFERRED_VOLUMES_CUSTOM_KEY.equals(key);
@SuppressWarnings("unchecked")
private final Map<String,Set<String>> parsedPreferredVolumes = Collections.synchronizedMap(new LRUMap(1000));
// TODO has to be lazily initialized currently because of the reliance on HdfsZooInstance. see ACCUMULO-3411
private volatile ServerConfigurationFactory serverConfs;
@Override
public String choose(VolumeChooserEnvironment env, String[] options) {
if (!env.hasTableId())
return super.choose(env, options);
// Get the current table's properties, and find the preferred volumes property
// This local variable is an intentional component of the single-check idiom.
ServerConfigurationFactory localConf = serverConfs;
if (localConf == null) {
// If we're under contention when first getting here we'll throw away some initializations.
localConf = new ServerConfigurationFactory(HdfsZooInstance.getInstance());
serverConfs = localConf;
}
TableConfiguration tableConf = localConf.getTableConfiguration(env.getTableId());
final Map<String,String> props = new HashMap<>();
tableConf.getProperties(props, PREFERRED_VOLUMES_FILTER);
if (props.isEmpty()) {
log.warn("No preferred volumes specified. Defaulting to randomly choosing from instance volumes");
return super.choose(env, options);
}
String volumes = props.get(PREFERRED_VOLUMES_CUSTOM_KEY);
if (log.isTraceEnabled()) {
log.trace("In custom chooser");
log.trace("Volumes: " + volumes);
log.trace("TableID: " + env.getTableId());
}
// If the preferred volumes property was specified, split the returned string by the comma and add use it to filter the given options.
Set<String> preferred = parsedPreferredVolumes.get(volumes);
if (preferred == null) {
preferred = new HashSet<>(Arrays.asList(StringUtils.split(volumes, ',')));
parsedPreferredVolumes.put(volumes, preferred);
}
// Only keep the options that are in the preferred set
final ArrayList<String> filteredOptions = new ArrayList<>(Arrays.asList(options));
filteredOptions.retainAll(preferred);
// If there are no preferred volumes left, then warn the user and choose randomly from the instance volumes
if (filteredOptions.isEmpty()) {
log.warn("Preferred volumes are not instance volumes. Defaulting to randomly choosing from instance volumes");
return super.choose(env, options);
}
// Randomly choose the volume from the preferred volumes
String choice = super.choose(env, filteredOptions.toArray(EMPTY_STRING_ARRAY));
if (log.isTraceEnabled()) {
log.trace("Choice = " + choice);
}
return choice;
}
}