blob: 0f102c83b7422bb4a56a8c1cd622fd940054eedf [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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.hadoop.hdds.scm.client;
import java.text.ParseException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.hadoop.hdds.annotation.InterfaceAudience;
import org.apache.hadoop.hdds.annotation.InterfaceStability;
import org.apache.hadoop.hdds.conf.ConfigurationSource;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.conf.RatisClientConfig;
import org.apache.hadoop.hdds.scm.container.common.helpers.StorageContainerException;
import org.apache.hadoop.io.retry.RetryPolicies;
import org.apache.hadoop.io.retry.RetryPolicy;
import org.apache.hadoop.ozone.OzoneConfigKeys;
import org.apache.hadoop.ozone.OzoneConsts;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import org.apache.ratis.protocol.AlreadyClosedException;
import org.apache.ratis.protocol.GroupMismatchException;
import org.apache.ratis.protocol.NotReplicatedException;
import org.apache.ratis.protocol.RaftRetryFailureException;
/**
* Utility methods for Ozone and Container Clients.
*
* The methods to retrieve SCM service endpoints assume there is a single
* SCM service instance. This will change when we switch to replicated service
* instances for redundancy.
*/
@InterfaceAudience.Public
@InterfaceStability.Unstable
public final class HddsClientUtils {
private HddsClientUtils() {
}
private static final List<Class<? extends Exception>> EXCEPTION_LIST =
ImmutableList.<Class<? extends Exception>>builder()
.add(TimeoutException.class)
.add(StorageContainerException.class)
.add(RaftRetryFailureException.class)
.add(AlreadyClosedException.class)
.add(GroupMismatchException.class)
// Not Replicated Exception will be thrown if watch For commit
// does not succeed
.add(NotReplicatedException.class)
.build();
/**
* Date format that used in ozone. Here the format is thread safe to use.
*/
private static final ThreadLocal<DateTimeFormatter> DATE_FORMAT =
ThreadLocal.withInitial(() -> {
DateTimeFormatter format =
DateTimeFormatter.ofPattern(OzoneConsts.OZONE_DATE_FORMAT);
return format.withZone(ZoneId.of(OzoneConsts.OZONE_TIME_ZONE));
});
/**
* Convert time in millisecond to a human readable format required in ozone.
* @return a human readable string for the input time
*/
public static String formatDateTime(long millis) {
ZonedDateTime dateTime = ZonedDateTime.ofInstant(
Instant.ofEpochMilli(millis), DATE_FORMAT.get().getZone());
return DATE_FORMAT.get().format(dateTime);
}
/**
* Convert time in ozone date format to millisecond.
* @return time in milliseconds
*/
public static long formatDateTime(String date) throws ParseException {
Preconditions.checkNotNull(date, "Date string should not be null.");
return ZonedDateTime.parse(date, DATE_FORMAT.get())
.toInstant().toEpochMilli();
}
private static void doNameChecks(String resName) {
if (resName == null) {
throw new IllegalArgumentException("Bucket or Volume name is null");
}
if (resName.length() < OzoneConsts.OZONE_MIN_BUCKET_NAME_LENGTH ||
resName.length() > OzoneConsts.OZONE_MAX_BUCKET_NAME_LENGTH) {
throw new IllegalArgumentException(
"Bucket or Volume length is illegal, "
+ "valid length is 3-63 characters");
}
if (resName.charAt(0) == '.' || resName.charAt(0) == '-') {
throw new IllegalArgumentException(
"Bucket or Volume name cannot start with a period or dash");
}
if (resName.charAt(resName.length() - 1) == '.' ||
resName.charAt(resName.length() - 1) == '-') {
throw new IllegalArgumentException("Bucket or Volume name "
+ "cannot end with a period or dash");
}
}
private static boolean isSupportedCharacter(char c) {
return (c == '.' || c == '-' ||
Character.isLowerCase(c) || Character.isDigit(c));
}
private static void doCharacterChecks(char currChar, char prev) {
if (Character.isUpperCase(currChar)) {
throw new IllegalArgumentException(
"Bucket or Volume name does not support uppercase characters");
}
if (!isSupportedCharacter(currChar)) {
throw new IllegalArgumentException("Bucket or Volume name has an " +
"unsupported character : " + currChar);
}
if (prev == '.' && currChar == '.') {
throw new IllegalArgumentException("Bucket or Volume name should not " +
"have two contiguous periods");
}
if (prev == '-' && currChar == '.') {
throw new IllegalArgumentException(
"Bucket or Volume name should not have period after dash");
}
if (prev == '.' && currChar == '-') {
throw new IllegalArgumentException(
"Bucket or Volume name should not have dash after period");
}
}
/**
* verifies that bucket name / volume name is a valid DNS name.
*
* @param resName Bucket or volume Name to be validated
*
* @throws IllegalArgumentException
*/
public static void verifyResourceName(String resName) {
doNameChecks(resName);
boolean isIPv4 = true;
char prev = (char) 0;
for (int index = 0; index < resName.length(); index++) {
char currChar = resName.charAt(index);
if (currChar != '.') {
isIPv4 = ((currChar >= '0') && (currChar <= '9')) && isIPv4;
}
doCharacterChecks(currChar, prev);
prev = currChar;
}
if (isIPv4) {
throw new IllegalArgumentException(
"Bucket or Volume name cannot be an IPv4 address or all numeric");
}
}
/**
* verifies that bucket / volume name is a valid DNS name.
*
* @param resourceNames Array of bucket / volume names to be verified.
*/
public static void verifyResourceName(String... resourceNames) {
for (String resourceName : resourceNames) {
HddsClientUtils.verifyResourceName(resourceName);
}
}
/**
* Checks that object parameters passed as reference is not null.
*
* @param references Array of object references to be checked.
* @param <T>
*/
public static <T> void checkNotNull(T... references) {
for (T ref: references) {
Preconditions.checkNotNull(ref);
}
}
/**
* Returns the cache value to be used for list calls.
* @param conf Configuration object
* @return list cache size
*/
public static int getListCacheSize(ConfigurationSource conf) {
return conf.getInt(OzoneConfigKeys.OZONE_CLIENT_LIST_CACHE_SIZE,
OzoneConfigKeys.OZONE_CLIENT_LIST_CACHE_SIZE_DEFAULT);
}
/**
* Returns the s3VolumeName configured in ConfigurationSource.
* @param conf Configuration object
* @return s3 volume name
*/
public static String getS3VolumeName(ConfigurationSource conf) {
return conf.get(OzoneConfigKeys.OZONE_S3_VOLUME_NAME,
OzoneConfigKeys.OZONE_S3_VOLUME_NAME_DEFAULT);
}
/**
* Returns the maximum no of outstanding async requests to be handled by
* Standalone and Ratis client.
*/
public static int getMaxOutstandingRequests(ConfigurationSource config) {
return OzoneConfiguration.of(config)
.getObject(RatisClientConfig.class)
.getMaxOutstandingRequests();
}
// This will return the underlying exception after unwrapping
// the exception to see if it matches with expected exception
// list otherwise will return the exception back.
public static Throwable checkForException(Exception e) {
Throwable t = e;
while (t != null && t.getCause() != null) {
for (Class<? extends Exception> cls : getExceptionList()) {
if (cls.isInstance(t)) {
return t;
}
}
t = t.getCause();
}
return t;
}
public static RetryPolicy createRetryPolicy(int maxRetryCount,
long retryInterval) {
// retry with fixed sleep between retries
return RetryPolicies.retryUpToMaximumCountWithFixedSleep(
maxRetryCount, retryInterval, TimeUnit.MILLISECONDS);
}
public static Map<Class<? extends Throwable>,
RetryPolicy> getRetryPolicyByException(int maxRetryCount,
long retryInterval) {
Map<Class<? extends Throwable>, RetryPolicy> policyMap = new HashMap<>();
for (Class<? extends Exception> ex : EXCEPTION_LIST) {
if (ex == TimeoutException.class
|| ex == RaftRetryFailureException.class) {
// retry without sleep
policyMap.put(ex, createRetryPolicy(maxRetryCount, 0));
} else {
// retry with fixed sleep between retries
policyMap.put(ex, createRetryPolicy(maxRetryCount, retryInterval));
}
}
// Default retry policy
policyMap
.put(Exception.class, createRetryPolicy(maxRetryCount, retryInterval));
return policyMap;
}
public static List<Class<? extends Exception>> getExceptionList() {
return EXCEPTION_LIST;
}
}