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.hbase.http;
019
020import static org.junit.Assert.assertNotNull;
021import static org.junit.Assert.fail;
022
023import java.io.BufferedReader;
024import java.io.File;
025import java.io.IOException;
026import java.io.InputStream;
027import java.io.InputStreamReader;
028import java.net.MalformedURLException;
029import java.net.ServerSocket;
030import java.net.URI;
031import java.net.URL;
032import java.net.URLConnection;
033import java.nio.charset.StandardCharsets;
034import org.apache.hadoop.conf.Configuration;
035import org.apache.hadoop.net.NetUtils;
036import org.apache.hadoop.security.authorize.AccessControlList;
037import org.slf4j.Logger;
038import org.slf4j.LoggerFactory;
039
040/**
041 * This is a base class for functional tests of the {@link HttpServer}. The methods are static for
042 * other classes to import statically.
043 */
044public class HttpServerFunctionalTest {
045  private static final Logger LOG = LoggerFactory.getLogger(HttpServerFunctionalTest.class);
046  /** JVM property for the webapp test dir : {@value} */
047  public static final String TEST_BUILD_WEBAPPS = "test.build.webapps";
048  /** expected location of the test.build.webapps dir: {@value} */
049  private static final String BUILD_WEBAPPS_DIR = "src/test/resources/webapps";
050
051  /** name of the test webapp: {@value} */
052  private static final String TEST = "test";
053
054  /**
055   * Create but do not start the test webapp server. The test webapp dir is prepared/checked in
056   * advance.
057   * @return the server instance
058   * @throws IOException    if a problem occurs
059   * @throws AssertionError if a condition was not met
060   */
061  public static HttpServer createTestServer() throws IOException {
062    prepareTestWebapp();
063    return createServer(TEST);
064  }
065
066  /**
067   * Create but do not start the test webapp server. The test webapp dir is prepared/checked in
068   * advance.
069   * @param conf the server configuration to use
070   * @return the server instance
071   * @throws IOException    if a problem occurs
072   * @throws AssertionError if a condition was not met
073   */
074  public static HttpServer createTestServer(Configuration conf) throws IOException {
075    prepareTestWebapp();
076    return createServer(TEST, conf);
077  }
078
079  public static HttpServer createTestServer(Configuration conf, AccessControlList adminsAcl)
080    throws IOException {
081    prepareTestWebapp();
082    return createServer(TEST, conf, adminsAcl);
083  }
084
085  /**
086   * Create but do not start the test webapp server. The test webapp dir is prepared/checked in
087   * advance.
088   * @param conf the server configuration to use
089   * @return the server instance
090   * @throws IOException    if a problem occurs
091   * @throws AssertionError if a condition was not met
092   */
093  public static HttpServer createTestServer(Configuration conf, String[] pathSpecs)
094    throws IOException {
095    prepareTestWebapp();
096    return createServer(TEST, conf, pathSpecs);
097  }
098
099  public static HttpServer createTestServerWithSecurity(Configuration conf) throws IOException {
100    prepareTestWebapp();
101    return localServerBuilder(TEST).setFindPort(true).setConf(conf).setSecurityEnabled(true)
102      // InfoServer normally sets these for us
103      .setUsernameConfKey(HttpServer.HTTP_SPNEGO_AUTHENTICATION_PRINCIPAL_KEY)
104      .setKeytabConfKey(HttpServer.HTTP_SPNEGO_AUTHENTICATION_KEYTAB_KEY).build();
105  }
106
107  public static HttpServer createTestServerWithSecurityAndAcl(Configuration conf,
108    AccessControlList acl) throws IOException {
109    prepareTestWebapp();
110    return localServerBuilder(TEST).setFindPort(true).setConf(conf).setSecurityEnabled(true)
111      // InfoServer normally sets these for us
112      .setUsernameConfKey(HttpServer.HTTP_SPNEGO_AUTHENTICATION_PRINCIPAL_KEY)
113      .setKeytabConfKey(HttpServer.HTTP_SPNEGO_AUTHENTICATION_KEYTAB_KEY).setSecurityEnabled(true)
114      .setACL(acl).build();
115  }
116
117  /**
118   * Prepare the test webapp by creating the directory from the test properties fail if the
119   * directory cannot be created.
120   * @throws IOException    if an error occurred
121   * @throws AssertionError if a condition was not met
122   */
123  protected static void prepareTestWebapp() throws IOException {
124    String webapps = System.getProperty(TEST_BUILD_WEBAPPS, BUILD_WEBAPPS_DIR);
125    File testWebappDir = new File(webapps + File.separatorChar + TEST);
126    if (!testWebappDir.exists()) {
127      fail("Test webapp dir " + testWebappDir.getCanonicalPath() + " missing");
128    }
129  }
130
131  /**
132   * Create an HttpServer instance on the given address for the given webapp
133   * @param host to bind
134   * @param port to bind
135   * @return the server
136   * @throws IOException if it could not be created
137   */
138  public static HttpServer createServer(String host, int port) throws IOException {
139    prepareTestWebapp();
140    return new HttpServer.Builder().setName(TEST)
141      .addEndpoint(URI.create("http://" + host + ":" + port)).setFindPort(true).build();
142  }
143
144  /**
145   * Create an HttpServer instance for the given webapp
146   * @param webapp the webapp to work with
147   * @return the server
148   * @throws IOException if it could not be created
149   */
150  public static HttpServer createServer(String webapp) throws IOException {
151    return localServerBuilder(webapp).setFindPort(true).build();
152  }
153
154  /**
155   * Create an HttpServer instance for the given webapp
156   * @param webapp the webapp to work with
157   * @param conf   the configuration to use for the server
158   * @return the server
159   * @throws IOException if it could not be created
160   */
161  public static HttpServer createServer(String webapp, Configuration conf) throws IOException {
162    return localServerBuilder(webapp).setFindPort(true).setConf(conf).build();
163  }
164
165  public static HttpServer createServer(String webapp, Configuration conf,
166    AccessControlList adminsAcl) throws IOException {
167    return localServerBuilder(webapp).setFindPort(true).setConf(conf).setACL(adminsAcl).build();
168  }
169
170  private static HttpServer.Builder localServerBuilder(String webapp) {
171    return new HttpServer.Builder().setName(webapp).addEndpoint(URI.create("http://localhost:0"));
172  }
173
174  /**
175   * Create an HttpServer instance for the given webapp
176   * @param webapp    the webapp to work with
177   * @param conf      the configuration to use for the server
178   * @param pathSpecs the paths specifications the server will service
179   * @return the server
180   * @throws IOException if it could not be created
181   */
182  public static HttpServer createServer(String webapp, Configuration conf, String[] pathSpecs)
183    throws IOException {
184    return localServerBuilder(webapp).setFindPort(true).setConf(conf).setPathSpec(pathSpecs)
185      .build();
186  }
187
188  /**
189   * Create and start a server with the test webapp
190   * @return the newly started server
191   * @throws IOException    on any failure
192   * @throws AssertionError if a condition was not met
193   */
194  public static HttpServer createAndStartTestServer() throws IOException {
195    HttpServer server = createTestServer();
196    server.start();
197    return server;
198  }
199
200  /**
201   * If the server is non null, stop it
202   * @param server to stop
203   * @throws Exception on any failure
204   */
205  public static void stop(HttpServer server) throws Exception {
206    if (server != null) {
207      server.stop();
208    }
209  }
210
211  /**
212   * Pass in a server, return a URL bound to localhost and its port
213   * @param server server
214   * @return a URL bonded to the base of the server
215   * @throws MalformedURLException if the URL cannot be created.
216   */
217  public static URL getServerURL(HttpServer server) throws MalformedURLException {
218    assertNotNull("No server", server);
219    return new URL("http://" + NetUtils.getHostPortString(server.getConnectorAddress(0)));
220  }
221
222  /**
223   * Read in the content from a URL
224   * @param url URL To read
225   * @return the text from the output
226   * @throws IOException if something went wrong
227   */
228  protected static String readOutput(URL url) throws IOException {
229    StringBuilder out = new StringBuilder();
230    InputStream in = url.openConnection().getInputStream();
231    byte[] buffer = new byte[64 * 1024];
232    int len = in.read(buffer);
233    while (len > 0) {
234      out.append(new String(buffer, 0, len, StandardCharsets.UTF_8));
235      len = in.read(buffer);
236    }
237    return out.toString();
238  }
239
240  /**
241   * Recursively deletes a {@link File}.
242   */
243  protected static void deleteRecursively(File d) {
244    if (d.isDirectory()) {
245      for (String name : d.list()) {
246        File child = new File(d, name);
247        if (child.isFile()) {
248          child.delete();
249        } else {
250          deleteRecursively(child);
251        }
252      }
253    }
254    d.delete();
255  }
256
257  /**
258   * Picks a free port on the host by binding a Socket to '0'.
259   */
260  protected static int getFreePort() throws IOException {
261    ServerSocket s = new ServerSocket(0);
262    try {
263      s.setReuseAddress(true);
264      int port = s.getLocalPort();
265      return port;
266    } finally {
267      if (null != s) {
268        s.close();
269      }
270    }
271  }
272
273  /**
274   * access a url, ignoring some IOException such as the page does not exist
275   */
276  public static void access(String urlstring) throws IOException {
277    URL url = new URL(urlstring);
278
279    URLConnection connection = url.openConnection();
280    connection.connect();
281
282    try (BufferedReader in = new BufferedReader(
283      new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
284      for (; in.readLine() != null;) {
285        continue;
286      }
287    } catch (IOException ioe) {
288      LOG.info("Got exception: ", ioe);
289    }
290  }
291
292}