001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hdfs.shortcircuit;
019
020import java.io.IOException;
021import java.net.InetSocketAddress;
022import java.util.concurrent.TimeUnit;
023
024import com.google.common.annotations.VisibleForTesting;
025import org.apache.commons.io.IOUtils;
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028import org.apache.hadoop.HadoopIllegalArgumentException;
029import org.apache.hadoop.hdfs.DFSClient;
030import org.apache.hadoop.hdfs.DFSClient.Conf;
031import org.apache.hadoop.hdfs.DFSConfigKeys;
032import org.apache.hadoop.net.unix.DomainSocket;
033
034import com.google.common.base.Preconditions;
035import com.google.common.cache.Cache;
036import com.google.common.cache.CacheBuilder;
037import org.apache.hadoop.util.PerformanceAdvisory;
038
039public class DomainSocketFactory {
040  private static final Log LOG = LogFactory.getLog(DomainSocketFactory.class);
041
042  public enum PathState {
043    UNUSABLE(false, false),
044    SHORT_CIRCUIT_DISABLED(true, false),
045    VALID(true, true);
046
047    PathState(boolean usableForDataTransfer, boolean usableForShortCircuit) {
048      this.usableForDataTransfer = usableForDataTransfer;
049      this.usableForShortCircuit = usableForShortCircuit;
050    }
051
052    public boolean getUsableForDataTransfer() {
053      return usableForDataTransfer;
054    }
055
056    public boolean getUsableForShortCircuit() {
057      return usableForShortCircuit;
058    }
059
060    private final boolean usableForDataTransfer;
061    private final boolean usableForShortCircuit;
062  }
063
064  public static class PathInfo {
065    private final static PathInfo NOT_CONFIGURED =
066          new PathInfo("", PathState.UNUSABLE);
067
068    final private String path;
069    final private PathState state;
070
071    PathInfo(String path, PathState state) {
072      this.path = path;
073      this.state = state;
074    }
075
076    public String getPath() {
077      return path;
078    }
079
080    public PathState getPathState() {
081      return state;
082    }
083    
084    @Override
085    public String toString() {
086      return new StringBuilder().append("PathInfo{path=").append(path).
087          append(", state=").append(state).append("}").toString();
088    }
089  }
090
091  /**
092   * Information about domain socket paths.
093   */
094  final Cache<String, PathState> pathMap =
095      CacheBuilder.newBuilder()
096      .expireAfterWrite(10, TimeUnit.MINUTES)
097      .build();
098
099  public DomainSocketFactory(Conf conf) {
100    final String feature;
101    if (conf.isShortCircuitLocalReads() && (!conf.isUseLegacyBlockReaderLocal())) {
102      feature = "The short-circuit local reads feature";
103    } else if (conf.isDomainSocketDataTraffic()) {
104      feature = "UNIX domain socket data traffic";
105    } else {
106      feature = null;
107    }
108
109    if (feature == null) {
110      PerformanceAdvisory.LOG.debug(
111          "Both short-circuit local reads and UNIX domain socket are disabled.");
112    } else {
113      if (conf.getDomainSocketPath().isEmpty()) {
114        throw new HadoopIllegalArgumentException(feature + " is enabled but "
115            + DFSConfigKeys.DFS_DOMAIN_SOCKET_PATH_KEY + " is not set.");
116      } else if (DomainSocket.getLoadingFailureReason() != null) {
117        LOG.warn(feature + " cannot be used because "
118            + DomainSocket.getLoadingFailureReason());
119      } else {
120        LOG.debug(feature + " is enabled.");
121      }
122    }
123  }
124
125  /**
126   * Get information about a domain socket path.
127   *
128   * @param addr         The inet address to use.
129   * @param conf         The client configuration.
130   *
131   * @return             Information about the socket path.
132   */
133  public PathInfo getPathInfo(InetSocketAddress addr, DFSClient.Conf conf) {
134    // If there is no domain socket path configured, we can't use domain
135    // sockets.
136    if (conf.getDomainSocketPath().isEmpty()) return PathInfo.NOT_CONFIGURED;
137    // If we can't do anything with the domain socket, don't create it.
138    if (!conf.isDomainSocketDataTraffic() &&
139        (!conf.isShortCircuitLocalReads() || conf.isUseLegacyBlockReaderLocal())) {
140      return PathInfo.NOT_CONFIGURED;
141    }
142    // If the DomainSocket code is not loaded, we can't create
143    // DomainSocket objects.
144    if (DomainSocket.getLoadingFailureReason() != null) {
145      return PathInfo.NOT_CONFIGURED;
146    }
147    // UNIX domain sockets can only be used to talk to local peers
148    if (!DFSClient.isLocalAddress(addr)) return PathInfo.NOT_CONFIGURED;
149    String escapedPath = DomainSocket.getEffectivePath(
150        conf.getDomainSocketPath(), addr.getPort());
151    PathState status = pathMap.getIfPresent(escapedPath);
152    if (status == null) {
153      return new PathInfo(escapedPath, PathState.VALID);
154    } else {
155      return new PathInfo(escapedPath, status);
156    }
157  }
158
159  public DomainSocket createSocket(PathInfo info, int socketTimeout) {
160    Preconditions.checkArgument(info.getPathState() != PathState.UNUSABLE);
161    boolean success = false;
162    DomainSocket sock = null;
163    try {
164      sock = DomainSocket.connect(info.getPath());
165      sock.setAttribute(DomainSocket.RECEIVE_TIMEOUT, socketTimeout);
166      success = true;
167    } catch (IOException e) {
168      LOG.warn("error creating DomainSocket", e);
169      // fall through
170    } finally {
171      if (!success) {
172        if (sock != null) {
173          IOUtils.closeQuietly(sock);
174        }
175        pathMap.put(info.getPath(), PathState.UNUSABLE);
176        sock = null;
177      }
178    }
179    return sock;
180  }
181
182  public void disableShortCircuitForPath(String path) {
183    pathMap.put(path, PathState.SHORT_CIRCUIT_DISABLED);
184  }
185
186  public void disableDomainSocketPath(String path) {
187    pathMap.put(path, PathState.UNUSABLE);
188  }
189
190  @VisibleForTesting
191  public void clearPathMap() {
192    pathMap.invalidateAll();
193  }
194}