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.server.datanode.web;
019
020import io.netty.bootstrap.ChannelFactory;
021import io.netty.bootstrap.ServerBootstrap;
022import io.netty.channel.ChannelFuture;
023import io.netty.channel.ChannelInitializer;
024import io.netty.channel.ChannelPipeline;
025import io.netty.channel.EventLoopGroup;
026import io.netty.channel.nio.NioEventLoopGroup;
027import io.netty.channel.socket.SocketChannel;
028import io.netty.channel.socket.nio.NioServerSocketChannel;
029import io.netty.handler.codec.http.HttpRequestDecoder;
030import io.netty.handler.codec.http.HttpResponseEncoder;
031import io.netty.handler.ssl.SslHandler;
032import io.netty.handler.stream.ChunkedWriteHandler;
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035import org.apache.hadoop.conf.Configuration;
036import org.apache.hadoop.fs.permission.FsPermission;
037import org.apache.hadoop.hdfs.DFSUtil;
038import org.apache.hadoop.hdfs.server.common.JspHelper;
039import org.apache.hadoop.hdfs.server.datanode.BlockScanner;
040import org.apache.hadoop.hdfs.server.datanode.DataNode;
041import org.apache.hadoop.hdfs.server.namenode.FileChecksumServlets;
042import org.apache.hadoop.hdfs.server.namenode.StreamFile;
043import org.apache.hadoop.http.HttpConfig;
044import org.apache.hadoop.http.HttpServer2;
045import org.apache.hadoop.net.NetUtils;
046import org.apache.hadoop.security.authorize.AccessControlList;
047import org.apache.hadoop.security.ssl.SSLFactory;
048
049import java.io.Closeable;
050import java.io.IOException;
051import java.net.InetSocketAddress;
052import java.net.SocketAddress;
053import java.net.URI;
054import java.nio.channels.ServerSocketChannel;
055import java.security.GeneralSecurityException;
056
057import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_ADMIN;
058import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_DATANODE_HTTPS_ADDRESS_DEFAULT;
059import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_DATANODE_HTTPS_ADDRESS_KEY;
060import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_DATANODE_HTTP_ADDRESS_KEY;
061
062public class DatanodeHttpServer implements Closeable {
063  private final HttpServer2 infoServer;
064  private final EventLoopGroup bossGroup;
065  private final EventLoopGroup workerGroup;
066  private final ServerSocketChannel externalHttpChannel;
067  private final ServerBootstrap httpServer;
068  private final SSLFactory sslFactory;
069  private final ServerBootstrap httpsServer;
070  private final Configuration conf;
071  private final Configuration confForCreate;
072  private InetSocketAddress httpAddress;
073  private InetSocketAddress httpsAddress;
074
075  static final Log LOG = LogFactory.getLog(DatanodeHttpServer.class);
076
077  public DatanodeHttpServer(final Configuration conf,
078      final DataNode datanode,
079      final ServerSocketChannel externalHttpChannel)
080    throws IOException {
081    this.conf = conf;
082
083    Configuration confForInfoServer = new Configuration(conf);
084    confForInfoServer.setInt(HttpServer2.HTTP_MAX_THREADS, 10);
085    HttpServer2.Builder builder = new HttpServer2.Builder()
086        .setName("datanode")
087        .setConf(confForInfoServer)
088        .setACL(new AccessControlList(conf.get(DFS_ADMIN, " ")))
089        .hostName(getHostnameForSpnegoPrincipal(confForInfoServer))
090        .addEndpoint(URI.create("http://localhost:0"))
091        .setFindPort(true);
092
093    this.infoServer = builder.build();
094    this.infoServer.setAttribute(HttpServer2.CONF_CONTEXT_ATTRIBUTE, conf);
095
096    this.infoServer.addInternalServlet(null, "/streamFile/*", StreamFile.class);
097    this.infoServer.addInternalServlet(null, "/getFileChecksum/*",
098        FileChecksumServlets.GetServlet.class);
099
100    this.infoServer.setAttribute("datanode", datanode);
101    this.infoServer.setAttribute(JspHelper.CURRENT_CONF, conf);
102    this.infoServer.addServlet(null, "/blockScannerReport",
103                               BlockScanner.Servlet.class);
104
105    this.infoServer.start();
106    final InetSocketAddress jettyAddr = infoServer.getConnectorAddress(0);
107
108    this.confForCreate = new Configuration(conf);
109    confForCreate.set(FsPermission.UMASK_LABEL, "000");
110
111    this.bossGroup = new NioEventLoopGroup();
112    this.workerGroup = new NioEventLoopGroup();
113    this.externalHttpChannel = externalHttpChannel;
114    HttpConfig.Policy policy = DFSUtil.getHttpPolicy(conf);
115
116    if (policy.isHttpEnabled()) {
117      this.httpServer = new ServerBootstrap().group(bossGroup, workerGroup)
118        .childHandler(new ChannelInitializer<SocketChannel>() {
119        @Override
120        protected void initChannel(SocketChannel ch) throws Exception {
121          ChannelPipeline p = ch.pipeline();
122          p.addLast(new HttpRequestDecoder(),
123            new HttpResponseEncoder(),
124            new ChunkedWriteHandler(),
125            new URLDispatcher(jettyAddr, conf, confForCreate));
126        }
127      });
128      if (externalHttpChannel == null) {
129        httpServer.channel(NioServerSocketChannel.class);
130      } else {
131        httpServer.channelFactory(new ChannelFactory<NioServerSocketChannel>() {
132          @Override
133          public NioServerSocketChannel newChannel() {
134            return new NioServerSocketChannel(externalHttpChannel) {
135              // The channel has been bounded externally via JSVC,
136              // thus bind() becomes a no-op.
137              @Override
138              protected void doBind(SocketAddress localAddress) throws Exception {}
139            };
140          }
141        });
142      }
143    } else {
144      this.httpServer = null;
145    }
146
147    if (policy.isHttpsEnabled()) {
148      this.sslFactory = new SSLFactory(SSLFactory.Mode.SERVER, conf);
149      try {
150        sslFactory.init();
151      } catch (GeneralSecurityException e) {
152        throw new IOException(e);
153      }
154      this.httpsServer = new ServerBootstrap().group(bossGroup, workerGroup)
155        .channel(NioServerSocketChannel.class)
156        .childHandler(new ChannelInitializer<SocketChannel>() {
157          @Override
158          protected void initChannel(SocketChannel ch) throws Exception {
159            ChannelPipeline p = ch.pipeline();
160            p.addLast(
161              new SslHandler(sslFactory.createSSLEngine()),
162              new HttpRequestDecoder(),
163              new HttpResponseEncoder(),
164              new ChunkedWriteHandler(),
165              new URLDispatcher(jettyAddr, conf, confForCreate));
166          }
167        });
168    } else {
169      this.httpsServer = null;
170      this.sslFactory = null;
171    }
172  }
173
174  public InetSocketAddress getHttpAddress() {
175    return httpAddress;
176  }
177
178  public InetSocketAddress getHttpsAddress() {
179    return httpsAddress;
180  }
181
182  public void start() {
183    if (httpServer != null) {
184      ChannelFuture f = httpServer.bind(DataNode.getInfoAddr(conf));
185      f.syncUninterruptibly();
186      httpAddress = (InetSocketAddress) f.channel().localAddress();
187      LOG.info("Listening HTTP traffic on " + httpAddress);
188    }
189
190    if (httpsServer != null) {
191      InetSocketAddress secInfoSocAddr = NetUtils.createSocketAddr(conf.getTrimmed(
192        DFS_DATANODE_HTTPS_ADDRESS_KEY, DFS_DATANODE_HTTPS_ADDRESS_DEFAULT));
193      ChannelFuture f = httpsServer.bind(secInfoSocAddr);
194      f.syncUninterruptibly();
195      httpsAddress = (InetSocketAddress) f.channel().localAddress();
196      LOG.info("Listening HTTPS traffic on " + httpsAddress);
197    }
198  }
199
200  @Override
201  public void close() throws IOException {
202    bossGroup.shutdownGracefully();
203    workerGroup.shutdownGracefully();
204    if (sslFactory != null) {
205      sslFactory.destroy();
206    }
207    if (externalHttpChannel != null) {
208      externalHttpChannel.close();
209    }
210    try {
211      infoServer.stop();
212    } catch (Exception e) {
213      throw new IOException(e);
214    }
215  }
216
217  private static String getHostnameForSpnegoPrincipal(Configuration conf) {
218    String addr = conf.getTrimmed(DFS_DATANODE_HTTP_ADDRESS_KEY, null);
219    if (addr == null) {
220      addr = conf.getTrimmed(DFS_DATANODE_HTTPS_ADDRESS_KEY,
221                             DFS_DATANODE_HTTPS_ADDRESS_DEFAULT);
222    }
223    InetSocketAddress inetSocker = NetUtils.createSocketAddr(addr);
224    return inetSocker.getHostString();
225  }
226}