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.hadoop.HadoopIllegalArgumentException;
027import org.apache.hadoop.hdfs.DFSUtilClient;
028import org.apache.hadoop.hdfs.client.HdfsClientConfigKeys;
029import org.apache.hadoop.hdfs.client.impl.DfsClientConf.ShortCircuitConf;
030import org.apache.hadoop.net.unix.DomainSocket;
031import org.apache.hadoop.util.PerformanceAdvisory;
032
033import com.google.common.base.Preconditions;
034import com.google.common.cache.Cache;
035import com.google.common.cache.CacheBuilder;
036
037import org.slf4j.Logger;
038import org.slf4j.LoggerFactory;
039
040public class DomainSocketFactory {
041  private static final Logger LOG = LoggerFactory.getLogger(
042      DomainSocketFactory.class);
043
044  public enum PathState {
045    UNUSABLE(false, false),
046    SHORT_CIRCUIT_DISABLED(true, false),
047    VALID(true, true);
048
049    PathState(boolean usableForDataTransfer, boolean usableForShortCircuit) {
050      this.usableForDataTransfer = usableForDataTransfer;
051      this.usableForShortCircuit = usableForShortCircuit;
052    }
053
054    public boolean getUsableForDataTransfer() {
055      return usableForDataTransfer;
056    }
057
058    public boolean getUsableForShortCircuit() {
059      return usableForShortCircuit;
060    }
061
062    private final boolean usableForDataTransfer;
063    private final boolean usableForShortCircuit;
064  }
065
066  public static class PathInfo {
067    private final static PathInfo NOT_CONFIGURED =
068          new PathInfo("", PathState.UNUSABLE);
069
070    final private String path;
071    final private PathState state;
072
073    PathInfo(String path, PathState state) {
074      this.path = path;
075      this.state = state;
076    }
077
078    public String getPath() {
079      return path;
080    }
081
082    public PathState getPathState() {
083      return state;
084    }
085
086    @Override
087    public String toString() {
088      return "PathInfo{path=" + path + ", state=" + state + "}";
089    }
090  }
091
092  /**
093   * Information about domain socket paths.
094   */
095  final Cache<String, PathState> pathMap =
096      CacheBuilder.newBuilder()
097      .expireAfterWrite(10, TimeUnit.MINUTES)
098      .build();
099
100  public DomainSocketFactory(ShortCircuitConf conf) {
101    final String feature;
102    if (conf.isShortCircuitLocalReads() && (!conf.isUseLegacyBlockReaderLocal())) {
103      feature = "The short-circuit local reads feature";
104    } else if (conf.isDomainSocketDataTraffic()) {
105      feature = "UNIX domain socket data traffic";
106    } else {
107      feature = null;
108    }
109
110    if (feature == null) {
111      PerformanceAdvisory.LOG.debug(
112          "Both short-circuit local reads and UNIX domain socket are disabled.");
113    } else {
114      if (conf.getDomainSocketPath().isEmpty()) {
115        throw new HadoopIllegalArgumentException(feature + " is enabled but "
116            + HdfsClientConfigKeys.DFS_DOMAIN_SOCKET_PATH_KEY + " is not set.");
117      } else if (DomainSocket.getLoadingFailureReason() != null) {
118        LOG.warn(feature + " cannot be used because "
119            + DomainSocket.getLoadingFailureReason());
120      } else {
121        LOG.debug(feature + " is enabled.");
122      }
123    }
124  }
125
126  /**
127   * Get information about a domain socket path.
128   *
129   * @param addr         The inet address to use.
130   * @param conf         The client configuration.
131   *
132   * @return             Information about the socket path.
133   */
134  public PathInfo getPathInfo(InetSocketAddress addr, ShortCircuitConf conf) {
135    // If there is no domain socket path configured, we can't use domain
136    // sockets.
137    if (conf.getDomainSocketPath().isEmpty()) return PathInfo.NOT_CONFIGURED;
138    // If we can't do anything with the domain socket, don't create it.
139    if (!conf.isDomainSocketDataTraffic() &&
140        (!conf.isShortCircuitLocalReads() || conf.isUseLegacyBlockReaderLocal())) {
141      return PathInfo.NOT_CONFIGURED;
142    }
143    // If the DomainSocket code is not loaded, we can't create
144    // DomainSocket objects.
145    if (DomainSocket.getLoadingFailureReason() != null) {
146      return PathInfo.NOT_CONFIGURED;
147    }
148    // UNIX domain sockets can only be used to talk to local peers
149    if (!DFSUtilClient.isLocalAddress(addr)) return PathInfo.NOT_CONFIGURED;
150    String escapedPath = DomainSocket.getEffectivePath(
151        conf.getDomainSocketPath(), addr.getPort());
152    PathState status = pathMap.getIfPresent(escapedPath);
153    if (status == null) {
154      return new PathInfo(escapedPath, PathState.VALID);
155    } else {
156      return new PathInfo(escapedPath, status);
157    }
158  }
159
160  public DomainSocket createSocket(PathInfo info, int socketTimeout) {
161    Preconditions.checkArgument(info.getPathState() != PathState.UNUSABLE);
162    boolean success = false;
163    DomainSocket sock = null;
164    try {
165      sock = DomainSocket.connect(info.getPath());
166      sock.setAttribute(DomainSocket.RECEIVE_TIMEOUT, socketTimeout);
167      success = true;
168    } catch (IOException e) {
169      LOG.warn("error creating DomainSocket", e);
170      // fall through
171    } finally {
172      if (!success) {
173        if (sock != null) {
174          IOUtils.closeQuietly(sock);
175        }
176        pathMap.put(info.getPath(), PathState.UNUSABLE);
177        sock = null;
178      }
179    }
180    return sock;
181  }
182
183  public void disableShortCircuitForPath(String path) {
184    pathMap.put(path, PathState.SHORT_CIRCUIT_DISABLED);
185  }
186
187  public void disableDomainSocketPath(String path) {
188    pathMap.put(path, PathState.UNUSABLE);
189  }
190
191  @VisibleForTesting
192  public void clearPathMap() {
193    pathMap.invalidateAll();
194  }
195}