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
095    this.infoServer.addInternalServlet(null, "/streamFile/*", StreamFile.class);
096    this.infoServer.addInternalServlet(null, "/getFileChecksum/*",
097        FileChecksumServlets.GetServlet.class);
098
099    this.infoServer.setAttribute("datanode", datanode);
100    this.infoServer.setAttribute(JspHelper.CURRENT_CONF, conf);
101    this.infoServer.addServlet(null, "/blockScannerReport",
102                               BlockScanner.Servlet.class);
103
104    this.infoServer.start();
105    final InetSocketAddress jettyAddr = infoServer.getConnectorAddress(0);
106
107    this.confForCreate = new Configuration(conf);
108    confForCreate.set(FsPermission.UMASK_LABEL, "000");
109
110    this.bossGroup = new NioEventLoopGroup();
111    this.workerGroup = new NioEventLoopGroup();
112    this.externalHttpChannel = externalHttpChannel;
113    HttpConfig.Policy policy = DFSUtil.getHttpPolicy(conf);
114
115    if (policy.isHttpEnabled()) {
116      this.httpServer = new ServerBootstrap().group(bossGroup, workerGroup)
117        .childHandler(new ChannelInitializer<SocketChannel>() {
118        @Override
119        protected void initChannel(SocketChannel ch) throws Exception {
120          ChannelPipeline p = ch.pipeline();
121          p.addLast(new HttpRequestDecoder(),
122            new HttpResponseEncoder(),
123            new ChunkedWriteHandler(),
124            new URLDispatcher(jettyAddr, conf, confForCreate));
125        }
126      });
127      if (externalHttpChannel == null) {
128        httpServer.channel(NioServerSocketChannel.class);
129      } else {
130        httpServer.channelFactory(new ChannelFactory<NioServerSocketChannel>() {
131          @Override
132          public NioServerSocketChannel newChannel() {
133            return new NioServerSocketChannel(externalHttpChannel) {
134              // The channel has been bounded externally via JSVC,
135              // thus bind() becomes a no-op.
136              @Override
137              protected void doBind(SocketAddress localAddress) throws Exception {}
138            };
139          }
140        });
141      }
142    } else {
143      this.httpServer = null;
144    }
145
146    if (policy.isHttpsEnabled()) {
147      this.sslFactory = new SSLFactory(SSLFactory.Mode.SERVER, conf);
148      try {
149        sslFactory.init();
150      } catch (GeneralSecurityException e) {
151        throw new IOException(e);
152      }
153      this.httpsServer = new ServerBootstrap().group(bossGroup, workerGroup)
154        .channel(NioServerSocketChannel.class)
155        .childHandler(new ChannelInitializer<SocketChannel>() {
156          @Override
157          protected void initChannel(SocketChannel ch) throws Exception {
158            ChannelPipeline p = ch.pipeline();
159            p.addLast(
160              new SslHandler(sslFactory.createSSLEngine()),
161              new HttpRequestDecoder(),
162              new HttpResponseEncoder(),
163              new ChunkedWriteHandler(),
164              new URLDispatcher(jettyAddr, conf, confForCreate));
165          }
166        });
167    } else {
168      this.httpsServer = null;
169      this.sslFactory = null;
170    }
171  }
172
173  public InetSocketAddress getHttpAddress() {
174    return httpAddress;
175  }
176
177  public InetSocketAddress getHttpsAddress() {
178    return httpsAddress;
179  }
180
181  public void start() {
182    if (httpServer != null) {
183      ChannelFuture f = httpServer.bind(DataNode.getInfoAddr(conf));
184      f.syncUninterruptibly();
185      httpAddress = (InetSocketAddress) f.channel().localAddress();
186      LOG.info("Listening HTTP traffic on " + httpAddress);
187    }
188
189    if (httpsServer != null) {
190      InetSocketAddress secInfoSocAddr = NetUtils.createSocketAddr(conf.getTrimmed(
191        DFS_DATANODE_HTTPS_ADDRESS_KEY, DFS_DATANODE_HTTPS_ADDRESS_DEFAULT));
192      ChannelFuture f = httpsServer.bind(secInfoSocAddr);
193      f.syncUninterruptibly();
194      httpsAddress = (InetSocketAddress) f.channel().localAddress();
195      LOG.info("Listening HTTPS traffic on " + httpsAddress);
196    }
197  }
198
199  @Override
200  public void close() throws IOException {
201    bossGroup.shutdownGracefully();
202    workerGroup.shutdownGracefully();
203    if (sslFactory != null) {
204      sslFactory.destroy();
205    }
206    if (externalHttpChannel != null) {
207      externalHttpChannel.close();
208    }
209    try {
210      infoServer.stop();
211    } catch (Exception e) {
212      throw new IOException(e);
213    }
214  }
215
216  private static String getHostnameForSpnegoPrincipal(Configuration conf) {
217    String addr = conf.getTrimmed(DFS_DATANODE_HTTP_ADDRESS_KEY, null);
218    if (addr == null) {
219      addr = conf.getTrimmed(DFS_DATANODE_HTTPS_ADDRESS_KEY,
220                             DFS_DATANODE_HTTPS_ADDRESS_DEFAULT);
221    }
222    InetSocketAddress inetSocker = NetUtils.createSocketAddr(addr);
223    return inetSocker.getHostString();
224  }
225}