1 /**
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 
19 package org.apache.hadoop.hdfs.web;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertNotNull;
23 import static org.junit.Assert.assertTrue;
24 
25 import java.io.File;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.net.HttpURLConnection;
29 import java.net.URI;
30 import java.net.URISyntaxException;
31 import java.net.URL;
32 import java.net.URLConnection;
33 
34 import org.apache.hadoop.conf.Configuration;
35 import org.apache.hadoop.fs.BlockLocation;
36 import org.apache.hadoop.fs.FSDataInputStream;
37 import org.apache.hadoop.fs.FSDataOutputStream;
38 import org.apache.hadoop.fs.FileStatus;
39 import org.apache.hadoop.fs.FileSystem;
40 import org.apache.hadoop.fs.FileUtil;
41 import org.apache.hadoop.fs.Path;
42 import org.apache.hadoop.hdfs.DFSConfigKeys;
43 import org.apache.hadoop.hdfs.MiniDFSCluster;
44 import org.apache.hadoop.hdfs.server.datanode.DataNode;
45 import org.apache.hadoop.hdfs.server.datanode.DataNodeTestUtils;
46 import org.apache.hadoop.hdfs.server.protocol.DatanodeRegistration;
47 import org.apache.hadoop.security.ssl.KeyStoreTestUtil;
48 import org.apache.hadoop.util.ServletUtil;
49 import org.junit.After;
50 import org.junit.AfterClass;
51 import org.junit.Before;
52 import org.junit.BeforeClass;
53 import org.junit.Test;
54 
55 public class TestHftpFileSystem {
56   private static final String BASEDIR = System.getProperty("test.build.dir",
57       "target/test-dir") + "/" + TestHftpFileSystem.class.getSimpleName();
58   private static String keystoresDir;
59   private static String sslConfDir;
60   private static Configuration config = null;
61   private static MiniDFSCluster cluster = null;
62   private static String blockPoolId = null;
63   private static String hftpUri = null;
64   private FileSystem hdfs = null;
65   private HftpFileSystem hftpFs = null;
66 
67   private static final Path[] TEST_PATHS = new Path[] {
68       // URI does not encode, Request#getPathInfo returns /foo
69       new Path("/foo;bar"),
70 
71       // URI does not encode, Request#getPathInfo returns verbatim
72       new Path("/foo+"), new Path("/foo+bar/foo+bar"),
73       new Path("/foo=bar/foo=bar"), new Path("/foo,bar/foo,bar"),
74       new Path("/foo@bar/foo@bar"), new Path("/foo&bar/foo&bar"),
75       new Path("/foo$bar/foo$bar"), new Path("/foo_bar/foo_bar"),
76       new Path("/foo~bar/foo~bar"), new Path("/foo.bar/foo.bar"),
77       new Path("/foo../bar/foo../bar"), new Path("/foo.../bar/foo.../bar"),
78       new Path("/foo'bar/foo'bar"),
79       new Path("/foo#bar/foo#bar"),
80       new Path("/foo!bar/foo!bar"),
81       // HDFS file names may not contain ":"
82 
83       // URI percent encodes, Request#getPathInfo decodes
84       new Path("/foo bar/foo bar"), new Path("/foo?bar/foo?bar"),
85       new Path("/foo\">bar/foo\">bar"), };
86 
87   @BeforeClass
setUp()88   public static void setUp() throws Exception {
89     config = new Configuration();
90     cluster = new MiniDFSCluster.Builder(config).numDataNodes(2).build();
91     blockPoolId = cluster.getNamesystem().getBlockPoolId();
92     hftpUri = "hftp://"
93         + config.get(DFSConfigKeys.DFS_NAMENODE_HTTP_ADDRESS_KEY);
94     File base = new File(BASEDIR);
95     FileUtil.fullyDelete(base);
96     base.mkdirs();
97     keystoresDir = new File(BASEDIR).getAbsolutePath();
98     sslConfDir = KeyStoreTestUtil.getClasspathDir(TestHftpFileSystem.class);
99 
100     KeyStoreTestUtil.setupSSLConfig(keystoresDir, sslConfDir, config, false);
101   }
102 
103   @AfterClass
tearDown()104   public static void tearDown() throws Exception {
105     if (cluster != null) {
106       cluster.shutdown();
107     }
108     FileUtil.fullyDelete(new File(BASEDIR));
109     KeyStoreTestUtil.cleanupSSLConfig(keystoresDir, sslConfDir);
110   }
111 
112   @Before
initFileSystems()113   public void initFileSystems() throws IOException {
114     hdfs = cluster.getFileSystem();
115     hftpFs = (HftpFileSystem) new Path(hftpUri).getFileSystem(config);
116     // clear out the namespace
117     for (FileStatus stat : hdfs.listStatus(new Path("/"))) {
118       hdfs.delete(stat.getPath(), true);
119     }
120   }
121 
122   @After
resetFileSystems()123   public void resetFileSystems() throws IOException {
124     FileSystem.closeAll();
125   }
126 
127   /**
128    * Test file creation and access with file names that need encoding.
129    */
130   @Test
testFileNameEncoding()131   public void testFileNameEncoding() throws IOException, URISyntaxException {
132     for (Path p : TEST_PATHS) {
133       // Create and access the path (data and streamFile servlets)
134       FSDataOutputStream out = hdfs.create(p, true);
135       out.writeBytes("0123456789");
136       out.close();
137       FSDataInputStream in = hftpFs.open(p);
138       assertEquals('0', in.read());
139       in.close();
140 
141       // Check the file status matches the path. Hftp returns a FileStatus
142       // with the entire URI, extract the path part.
143       assertEquals(p, new Path(hftpFs.getFileStatus(p).getPath().toUri()
144           .getPath()));
145 
146       // Test list status (listPath servlet)
147       assertEquals(1, hftpFs.listStatus(p).length);
148 
149       // Test content summary (contentSummary servlet)
150       assertNotNull("No content summary", hftpFs.getContentSummary(p));
151 
152       // Test checksums (fileChecksum and getFileChecksum servlets)
153       assertNotNull("No file checksum", hftpFs.getFileChecksum(p));
154     }
155   }
156 
testDataNodeRedirect(Path path)157   private void testDataNodeRedirect(Path path) throws IOException {
158     // Create the file
159     if (hdfs.exists(path)) {
160       hdfs.delete(path, true);
161     }
162     FSDataOutputStream out = hdfs.create(path, (short) 1);
163     out.writeBytes("0123456789");
164     out.close();
165 
166     // Get the path's block location so we can determine
167     // if we were redirected to the right DN.
168     BlockLocation[] locations = hdfs.getFileBlockLocations(path, 0, 10);
169     String xferAddr = locations[0].getNames()[0];
170 
171     // Connect to the NN to get redirected
172     URL u = hftpFs.getNamenodeURL(
173         "/data" + ServletUtil.encodePath(path.toUri().getPath()),
174         "ugi=userx,groupy");
175     HttpURLConnection conn = (HttpURLConnection) u.openConnection();
176     HttpURLConnection.setFollowRedirects(true);
177     conn.connect();
178     conn.getInputStream();
179 
180     boolean checked = false;
181     // Find the datanode that has the block according to locations
182     // and check that the URL was redirected to this DN's info port
183     for (DataNode node : cluster.getDataNodes()) {
184       DatanodeRegistration dnR = DataNodeTestUtils.getDNRegistrationForBP(node,
185           blockPoolId);
186       if (dnR.getXferAddr().equals(xferAddr)) {
187         checked = true;
188         assertEquals(dnR.getInfoPort(), conn.getURL().getPort());
189       }
190     }
191     assertTrue("The test never checked that location of "
192         + "the block and hftp desitnation are the same", checked);
193   }
194 
195   /**
196    * Test that clients are redirected to the appropriate DN.
197    */
198   @Test
testDataNodeRedirect()199   public void testDataNodeRedirect() throws IOException {
200     for (Path p : TEST_PATHS) {
201       testDataNodeRedirect(p);
202     }
203   }
204 
205   /**
206    * Tests getPos() functionality.
207    */
208   @Test
testGetPos()209   public void testGetPos() throws IOException {
210     final Path testFile = new Path("/testfile+1");
211     // Write a test file.
212     FSDataOutputStream out = hdfs.create(testFile, true);
213     out.writeBytes("0123456789");
214     out.close();
215 
216     FSDataInputStream in = hftpFs.open(testFile);
217 
218     // Test read().
219     for (int i = 0; i < 5; ++i) {
220       assertEquals(i, in.getPos());
221       in.read();
222     }
223 
224     // Test read(b, off, len).
225     assertEquals(5, in.getPos());
226     byte[] buffer = new byte[10];
227     assertEquals(2, in.read(buffer, 0, 2));
228     assertEquals(7, in.getPos());
229 
230     // Test read(b).
231     int bytesRead = in.read(buffer);
232     assertEquals(7 + bytesRead, in.getPos());
233 
234     // Test EOF.
235     for (int i = 0; i < 100; ++i) {
236       in.read();
237     }
238     assertEquals(10, in.getPos());
239     in.close();
240   }
241 
242   /**
243    * Tests seek().
244    */
245   @Test
testSeek()246   public void testSeek() throws IOException {
247     final Path testFile = new Path("/testfile+1");
248     FSDataOutputStream out = hdfs.create(testFile, true);
249     out.writeBytes("0123456789");
250     out.close();
251     FSDataInputStream in = hftpFs.open(testFile);
252     in.seek(7);
253     assertEquals('7', in.read());
254     in.close();
255   }
256 
257   @Test
testReadClosedStream()258   public void testReadClosedStream() throws IOException {
259     final Path testFile = new Path("/testfile+2");
260     FSDataOutputStream os = hdfs.create(testFile, true);
261     os.writeBytes("0123456789");
262     os.close();
263 
264     // ByteRangeInputStream delays opens until reads. Make sure it doesn't
265     // open a closed stream that has never been opened
266     FSDataInputStream in = hftpFs.open(testFile);
267     in.close();
268     checkClosedStream(in);
269     checkClosedStream(in.getWrappedStream());
270 
271     // force the stream to connect and then close it
272     in = hftpFs.open(testFile);
273     int ch = in.read();
274     assertEquals('0', ch);
275     in.close();
276     checkClosedStream(in);
277     checkClosedStream(in.getWrappedStream());
278 
279     // make sure seeking doesn't automagically reopen the stream
280     in.seek(4);
281     checkClosedStream(in);
282     checkClosedStream(in.getWrappedStream());
283   }
284 
checkClosedStream(InputStream is)285   private void checkClosedStream(InputStream is) {
286     IOException ioe = null;
287     try {
288       is.read();
289     } catch (IOException e) {
290       ioe = e;
291     }
292     assertNotNull("No exception on closed read", ioe);
293     assertEquals("Stream closed", ioe.getMessage());
294   }
295 
296   @Test
testHftpDefaultPorts()297   public void testHftpDefaultPorts() throws IOException {
298     Configuration conf = new Configuration();
299     URI uri = URI.create("hftp://localhost");
300     HftpFileSystem fs = (HftpFileSystem) FileSystem.get(uri, conf);
301 
302     assertEquals(DFSConfigKeys.DFS_NAMENODE_HTTP_PORT_DEFAULT,
303         fs.getDefaultPort());
304 
305     assertEquals(uri, fs.getUri());
306 
307     // HFTP uses http to get the token so canonical service name should
308     // return the http port.
309     assertEquals("127.0.0.1:" + DFSConfigKeys.DFS_NAMENODE_HTTP_PORT_DEFAULT,
310         fs.getCanonicalServiceName());
311   }
312 
313   @Test
testHftpCustomDefaultPorts()314   public void testHftpCustomDefaultPorts() throws IOException {
315     Configuration conf = new Configuration();
316     conf.setInt(DFSConfigKeys.DFS_NAMENODE_HTTP_PORT_KEY, 123);
317 
318     URI uri = URI.create("hftp://localhost");
319     HftpFileSystem fs = (HftpFileSystem) FileSystem.get(uri, conf);
320 
321     assertEquals(123, fs.getDefaultPort());
322 
323     assertEquals(uri, fs.getUri());
324 
325     // HFTP uses http to get the token so canonical service name should
326     // return the http port.
327     assertEquals("127.0.0.1:123", fs.getCanonicalServiceName());
328   }
329 
330   @Test
testHftpCustomUriPortWithDefaultPorts()331   public void testHftpCustomUriPortWithDefaultPorts() throws IOException {
332     Configuration conf = new Configuration();
333     URI uri = URI.create("hftp://localhost:123");
334     HftpFileSystem fs = (HftpFileSystem) FileSystem.get(uri, conf);
335 
336     assertEquals(DFSConfigKeys.DFS_NAMENODE_HTTP_PORT_DEFAULT,
337         fs.getDefaultPort());
338 
339     assertEquals(uri, fs.getUri());
340     assertEquals("127.0.0.1:123", fs.getCanonicalServiceName());
341   }
342 
343   @Test
testHftpCustomUriPortWithCustomDefaultPorts()344   public void testHftpCustomUriPortWithCustomDefaultPorts() throws IOException {
345     Configuration conf = new Configuration();
346     conf.setInt(DFSConfigKeys.DFS_NAMENODE_HTTP_PORT_KEY, 123);
347 
348     URI uri = URI.create("hftp://localhost:789");
349     HftpFileSystem fs = (HftpFileSystem) FileSystem.get(uri, conf);
350 
351     assertEquals(123, fs.getDefaultPort());
352 
353     assertEquals(uri, fs.getUri());
354     assertEquals("127.0.0.1:789", fs.getCanonicalServiceName());
355   }
356 
357   @Test
testTimeout()358   public void testTimeout() throws IOException {
359     Configuration conf = new Configuration();
360     URI uri = URI.create("hftp://localhost");
361     HftpFileSystem fs = (HftpFileSystem) FileSystem.get(uri, conf);
362     URLConnection conn = fs.connectionFactory.openConnection(new URL(
363         "http://localhost"));
364     assertEquals(URLConnectionFactory.DEFAULT_SOCKET_TIMEOUT,
365         conn.getConnectTimeout());
366     assertEquals(URLConnectionFactory.DEFAULT_SOCKET_TIMEOUT,
367         conn.getReadTimeout());
368   }
369 
370   // /
371 
372   @Test
testHsftpDefaultPorts()373   public void testHsftpDefaultPorts() throws IOException {
374     Configuration conf = new Configuration();
375     URI uri = URI.create("hsftp://localhost");
376     HsftpFileSystem fs = (HsftpFileSystem) FileSystem.get(uri, conf);
377 
378     assertEquals(DFSConfigKeys.DFS_NAMENODE_HTTPS_PORT_DEFAULT,
379         fs.getDefaultPort());
380 
381     assertEquals(uri, fs.getUri());
382     assertEquals("127.0.0.1:" + DFSConfigKeys.DFS_NAMENODE_HTTPS_PORT_DEFAULT,
383         fs.getCanonicalServiceName());
384   }
385 
386   @Test
testHsftpCustomDefaultPorts()387   public void testHsftpCustomDefaultPorts() throws IOException {
388     Configuration conf = new Configuration();
389     conf.setInt(DFSConfigKeys.DFS_NAMENODE_HTTP_PORT_KEY, 123);
390     conf.setInt(DFSConfigKeys.DFS_NAMENODE_HTTPS_PORT_KEY, 456);
391 
392     URI uri = URI.create("hsftp://localhost");
393     HsftpFileSystem fs = (HsftpFileSystem) FileSystem.get(uri, conf);
394 
395     assertEquals(456, fs.getDefaultPort());
396 
397     assertEquals(uri, fs.getUri());
398     assertEquals("127.0.0.1:456", fs.getCanonicalServiceName());
399   }
400 
401   @Test
testHsftpCustomUriPortWithDefaultPorts()402   public void testHsftpCustomUriPortWithDefaultPorts() throws IOException {
403     Configuration conf = new Configuration();
404     URI uri = URI.create("hsftp://localhost:123");
405     HsftpFileSystem fs = (HsftpFileSystem) FileSystem.get(uri, conf);
406 
407     assertEquals(DFSConfigKeys.DFS_NAMENODE_HTTPS_PORT_DEFAULT,
408         fs.getDefaultPort());
409 
410     assertEquals(uri, fs.getUri());
411     assertEquals("127.0.0.1:123", fs.getCanonicalServiceName());
412   }
413 
414   @Test
testHsftpCustomUriPortWithCustomDefaultPorts()415   public void testHsftpCustomUriPortWithCustomDefaultPorts() throws IOException {
416     Configuration conf = new Configuration();
417     conf.setInt(DFSConfigKeys.DFS_NAMENODE_HTTP_PORT_KEY, 123);
418     conf.setInt(DFSConfigKeys.DFS_NAMENODE_HTTPS_PORT_KEY, 456);
419 
420     URI uri = URI.create("hsftp://localhost:789");
421     HsftpFileSystem fs = (HsftpFileSystem) FileSystem.get(uri, conf);
422 
423     assertEquals(456, fs.getDefaultPort());
424 
425     assertEquals(uri, fs.getUri());
426     assertEquals("127.0.0.1:789", fs.getCanonicalServiceName());
427   }
428 }
429