| /* |
| * 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); |
| } |
| } |
| } |