blob: 858b6b123570e0b7bea9045810b3f73fbb7e6c3d [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.hadoop.registry.client.binding;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.fs.PathNotFoundException;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.registry.client.api.RegistryConstants;
import org.apache.hadoop.registry.client.api.RegistryOperations;
import org.apache.hadoop.registry.client.exceptions.InvalidPathnameException;
import org.apache.hadoop.registry.client.exceptions.InvalidRecordException;
import org.apache.hadoop.registry.client.exceptions.NoRecordException;
import org.apache.hadoop.registry.client.impl.zk.RegistryInternalConstants;
import org.apache.hadoop.registry.client.types.RegistryPathStatus;
import org.apache.hadoop.registry.client.types.ServiceRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.hadoop.registry.client.binding.RegistryPathUtils.*;
import java.io.EOFException;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* Utility methods for working with a registry.
*/
@InterfaceAudience.Public
@InterfaceStability.Evolving
public class RegistryUtils {
private static final Logger LOG =
LoggerFactory.getLogger(RegistryUtils.class);
/**
* Buld the user path -switches to the system path if the user is "".
* It also cross-converts the username to ascii via punycode
* @param username username or ""
* @return the path to the user
*/
public static String homePathForUser(String username) {
Preconditions.checkArgument(username != null, "null user");
// catch recursion
if (username.startsWith(RegistryConstants.PATH_USERS)) {
return username;
}
if (username.isEmpty()) {
return RegistryConstants.PATH_SYSTEM_SERVICES;
}
// convert username to registry name
String convertedName = convertUsername(username);
return RegistryPathUtils.join(RegistryConstants.PATH_USERS,
encodeForRegistry(convertedName));
}
/**
* Convert the username to that which can be used for registry
* entries. Lower cases it,
* Strip the kerberos realm off a username if needed, and any "/" hostname
* entries
* @param username user
* @return the converted username
*/
public static String convertUsername(String username) {
String converted =
org.apache.hadoop.util.StringUtils.toLowerCase(username);
int atSymbol = converted.indexOf('@');
if (atSymbol > 0) {
converted = converted.substring(0, atSymbol);
}
int slashSymbol = converted.indexOf('/');
if (slashSymbol > 0) {
converted = converted.substring(0, slashSymbol);
}
return converted;
}
/**
* Create a service classpath
* @param user username or ""
* @param serviceClass service name
* @return a full path
*/
public static String serviceclassPath(String user,
String serviceClass) {
String services = join(homePathForUser(user),
RegistryConstants.PATH_USER_SERVICES);
return join(services,
serviceClass);
}
/**
* Create a path to a service under a user and service class
* @param user username or ""
* @param serviceClass service name
* @param serviceName service name unique for that user and service class
* @return a full path
*/
public static String servicePath(String user,
String serviceClass,
String serviceName) {
return join(
serviceclassPath(user, serviceClass),
serviceName);
}
/**
* Create a path for listing components under a service
* @param user username or ""
* @param serviceClass service name
* @param serviceName service name unique for that user and service class
* @return a full path
*/
public static String componentListPath(String user,
String serviceClass, String serviceName) {
return join(servicePath(user, serviceClass, serviceName),
RegistryConstants.SUBPATH_COMPONENTS);
}
/**
* Create the path to a service record for a component
* @param user username or ""
* @param serviceClass service name
* @param serviceName service name unique for that user and service class
* @param componentName unique name/ID of the component
* @return a full path
*/
public static String componentPath(String user,
String serviceClass, String serviceName, String componentName) {
return join(
componentListPath(user, serviceClass, serviceName),
componentName);
}
/**
* List service records directly under a path
* @param registryOperations registry operations instance
* @param path path to list
* @return a mapping of the service records that were resolved, indexed
* by their full path
* @throws IOException
*/
public static Map<String, ServiceRecord> listServiceRecords(
RegistryOperations registryOperations,
String path) throws IOException {
Map<String, RegistryPathStatus> children =
statChildren(registryOperations, path);
return extractServiceRecords(registryOperations,
path,
children.values());
}
/**
* List children of a directory and retrieve their
* {@link RegistryPathStatus} values.
* <p>
* This is not an atomic operation; A child may be deleted
* during the iteration through the child entries. If this happens,
* the <code>PathNotFoundException</code> is caught and that child
* entry ommitted.
*
* @param path path
* @return a possibly empty map of child entries listed by
* their short name.
* @throws PathNotFoundException path is not in the registry.
* @throws InvalidPathnameException the path is invalid.
* @throws IOException Any other IO Exception
*/
public static Map<String, RegistryPathStatus> statChildren(
RegistryOperations registryOperations,
String path)
throws PathNotFoundException,
InvalidPathnameException,
IOException {
List<String> childNames = registryOperations.list(path);
Map<String, RegistryPathStatus> results =
new HashMap<String, RegistryPathStatus>();
for (String childName : childNames) {
String child = join(path, childName);
try {
RegistryPathStatus stat = registryOperations.stat(child);
results.put(childName, stat);
} catch (PathNotFoundException pnfe) {
if (LOG.isDebugEnabled()) {
LOG.debug("stat failed on {}: moved? {}", child, pnfe, pnfe);
}
// and continue
}
}
return results;
}
/**
* Get the home path of the current user.
* <p>
* In an insecure cluster, the environment variable
* <code>HADOOP_USER_NAME</code> is queried <i>first</i>.
* <p>
* This means that in a YARN container where the creator set this
* environment variable to propagate their identity, the defined
* user name is used in preference to the actual user.
* <p>
* In a secure cluster, the kerberos identity of the current user is used.
* @return a path for the current user's home dir.
* @throws RuntimeException if the current user identity cannot be determined
* from the OS/kerberos.
*/
public static String homePathForCurrentUser() {
String shortUserName = currentUsernameUnencoded();
return homePathForUser(shortUserName);
}
/**
* Get the current username, before any encoding has been applied.
* @return the current user from the kerberos identity, falling back
* to the user and/or env variables.
*/
private static String currentUsernameUnencoded() {
String env_hadoop_username = System.getenv(
RegistryInternalConstants.HADOOP_USER_NAME);
return getCurrentUsernameUnencoded(env_hadoop_username);
}
/**
* Get the current username, using the value of the parameter
* <code>env_hadoop_username</code> if it is set on an insecure cluster.
* This ensures that the username propagates correctly across processes
* started by YARN.
* <p>
* This method is primarly made visible for testing.
* @param env_hadoop_username the environment variable
* @return the selected username
* @throws RuntimeException if there is a problem getting the short user
* name of the current user.
*/
@VisibleForTesting
public static String getCurrentUsernameUnencoded(String env_hadoop_username) {
String shortUserName = null;
if (!UserGroupInformation.isSecurityEnabled()) {
shortUserName = env_hadoop_username;
}
if (StringUtils.isEmpty(shortUserName)) {
try {
shortUserName = UserGroupInformation.getCurrentUser().getShortUserName();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return shortUserName;
}
/**
* Get the current user path formatted for the registry
* <p>
* In an insecure cluster, the environment variable
* <code>HADOOP_USER_NAME </code> is queried <i>first</i>.
* <p>
* This means that in a YARN container where the creator set this
* environment variable to propagate their identity, the defined
* user name is used in preference to the actual user.
* <p>
* In a secure cluster, the kerberos identity of the current user is used.
* @return the encoded shortname of the current user
* @throws RuntimeException if the current user identity cannot be determined
* from the OS/kerberos.
*
*/
public static String currentUser() {
String shortUserName = currentUsernameUnencoded();
return encodeForRegistry(shortUserName);
}
/**
* Extract all service records under a list of stat operations...this
* skips entries that are too short or simply not matching
* @param operations operation support for fetches
* @param parentpath path of the parent of all the entries
* @param stats Collection of stat results
* @return a possibly empty map of fullpath:record.
* @throws IOException for any IO Operation that wasn't ignored.
*/
public static Map<String, ServiceRecord> extractServiceRecords(
RegistryOperations operations,
String parentpath,
Collection<RegistryPathStatus> stats) throws IOException {
Map<String, ServiceRecord> results = new HashMap<String, ServiceRecord>(stats.size());
for (RegistryPathStatus stat : stats) {
if (stat.size > ServiceRecord.RECORD_TYPE.length()) {
// maybe has data
String path = join(parentpath, stat.path);
try {
ServiceRecord serviceRecord = operations.resolve(path);
results.put(path, serviceRecord);
} catch (EOFException ignored) {
if (LOG.isDebugEnabled()) {
LOG.debug("data too short for {}", path);
}
} catch (InvalidRecordException record) {
if (LOG.isDebugEnabled()) {
LOG.debug("Invalid record at {}", path);
}
} catch (NoRecordException record) {
if (LOG.isDebugEnabled()) {
LOG.debug("No record at {}", path);
}
}
}
}
return results;
}
/**
* Extract all service records under a list of stat operations...this
* non-atomic action skips entries that are too short or simply not matching.
* <p>
* @param operations operation support for fetches
* @param parentpath path of the parent of all the entries
* @return a possibly empty map of fullpath:record.
* @throws IOException for any IO Operation that wasn't ignored.
*/
public static Map<String, ServiceRecord> extractServiceRecords(
RegistryOperations operations,
String parentpath,
Map<String , RegistryPathStatus> stats) throws IOException {
return extractServiceRecords(operations, parentpath, stats.values());
}
/**
* Extract all service records under a list of stat operations...this
* non-atomic action skips entries that are too short or simply not matching.
* <p>
* @param operations operation support for fetches
* @param parentpath path of the parent of all the entries
* @return a possibly empty map of fullpath:record.
* @throws IOException for any IO Operation that wasn't ignored.
*/
public static Map<String, ServiceRecord> extractServiceRecords(
RegistryOperations operations,
String parentpath) throws IOException {
return
extractServiceRecords(operations,
parentpath,
statChildren(operations, parentpath).values());
}
/**
* Static instance of service record marshalling
*/
public static class ServiceRecordMarshal extends JsonSerDeser<ServiceRecord> {
public ServiceRecordMarshal() {
super(ServiceRecord.class);
}
}
}