blob: 3e3bf072116961186fcf6e6f51b96e167c55f217 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.util;
import static org.junit.Assert.*;
import static org.junit.Assume.assumeTrue;
import static org.junit.matchers.JUnitMatchers.containsString;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.fs.FileUtil;
import org.apache.hadoop.test.GenericTestUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.hamcrest.CoreMatchers.*;
/**
* Test cases for helper Windows winutils.exe utility.
*/
public class TestWinUtils {
private static final Logger LOG = LoggerFactory.getLogger(TestWinUtils.class);
private static File TEST_DIR = GenericTestUtils.getTestDir(
TestWinUtils.class.getSimpleName());
String winutils;
@Before
public void setUp() throws IOException {
// Not supported on non-Windows platforms
assumeTrue(Shell.WINDOWS);
TEST_DIR.mkdirs();
assertTrue("Failed to create Test directory " + TEST_DIR,
TEST_DIR.isDirectory() );
winutils = Shell.getWinUtilsPath();
}
@After
public void tearDown() throws IOException {
FileUtil.fullyDelete(TEST_DIR);
}
private void requireWinutils() throws IOException {
Shell.getWinUtilsPath();
}
// Helper routine that writes the given content to the file.
private void writeFile(File file, String content) throws IOException {
byte[] data = content.getBytes();
try (FileOutputStream os = new FileOutputStream(file)) {
os.write(data);
os.close();
}
}
// Helper routine that reads the first 100 bytes from the file.
private String readFile(File file) throws IOException {
byte[] b;
try (FileInputStream fos = new FileInputStream(file)) {
b = new byte[100];
int count = fos.read(b);
assertEquals(100, count);
}
return new String(b);
}
@Test (timeout = 30000)
public void testLs() throws IOException {
requireWinutils();
final String content = "6bytes";
final int contentSize = content.length();
File testFile = new File(TEST_DIR, "file1");
writeFile(testFile, content);
// Verify permissions and file name return tokens
String testPath = testFile.getCanonicalPath();
String output = Shell.execCommand(
winutils, "ls", testPath);
String[] outputArgs = output.split("[ \r\n]");
assertEquals("-rwx------", outputArgs[0]);
assertEquals(outputArgs[outputArgs.length - 1], testPath);
// Verify most tokens when using a formatted output (other tokens
// will be verified with chmod/chown)
output = Shell.execCommand(
winutils, "ls", "-F", testPath);
outputArgs = output.split("[|\r\n]");
assertEquals(9, outputArgs.length);
assertEquals("-rwx------", outputArgs[0]);
assertEquals(contentSize, Long.parseLong(outputArgs[4]));
assertEquals(outputArgs[8], testPath);
testFile.delete();
assertFalse(testFile.exists());
}
@Test (timeout = 30000)
public void testGroups() throws IOException {
requireWinutils();
String currentUser = System.getProperty("user.name");
// Verify that groups command returns information about the current user
// groups when invoked with no args
String outputNoArgs = Shell.execCommand(
winutils, "groups").trim();
String output = Shell.execCommand(
winutils, "groups", currentUser).trim();
assertEquals(output, outputNoArgs);
// Verify that groups command with the -F flag returns the same information
String outputFormat = Shell.execCommand(
winutils, "groups", "-F", currentUser).trim();
outputFormat = outputFormat.replace("|", " ");
assertEquals(output, outputFormat);
}
private void chmod(String mask, File file) throws IOException {
Shell.execCommand(
winutils, "chmod", mask, file.getCanonicalPath());
}
private void chmodR(String mask, File file) throws IOException {
Shell.execCommand(
winutils, "chmod", "-R", mask, file.getCanonicalPath());
}
private String ls(File file) throws IOException {
return Shell.execCommand(
winutils, "ls", file.getCanonicalPath());
}
private String lsF(File file) throws IOException {
return Shell.execCommand(
winutils, "ls", "-F", file.getCanonicalPath());
}
private void assertPermissions(File file, String expected)
throws IOException {
String output = ls(file).split("[ \r\n]")[0];
assertEquals(expected, output);
}
private void testChmodInternal(String mode, String expectedPerm)
throws IOException {
requireWinutils();
File a = new File(TEST_DIR, "file1");
assertTrue(a.createNewFile());
// Reset permissions on the file to default
chmod("700", a);
// Apply the mode mask
chmod(mode, a);
// Compare the output
assertPermissions(a, expectedPerm);
a.delete();
assertFalse(a.exists());
}
private void testNewFileChmodInternal(String expectedPerm) throws IOException {
requireWinutils();
// Create a new directory
File dir = new File(TEST_DIR, "dir1");
assertTrue(dir.mkdir());
// Set permission use chmod
chmod("755", dir);
// Create a child file in the directory
File child = new File(dir, "file1");
assertTrue(child.createNewFile());
// Verify the child file has correct permissions
assertPermissions(child, expectedPerm);
child.delete();
dir.delete();
assertFalse(dir.exists());
}
private void testChmodInternalR(String mode, String expectedPerm,
String expectedPermx) throws IOException {
requireWinutils();
// Setup test folder hierarchy
File a = new File(TEST_DIR, "a");
assertTrue(a.mkdir());
chmod("700", a);
File aa = new File(a, "a");
assertTrue(aa.createNewFile());
chmod("600", aa);
File ab = new File(a, "b");
assertTrue(ab.mkdir());
chmod("700", ab);
File aba = new File(ab, "a");
assertTrue(aba.mkdir());
chmod("700", aba);
File abb = new File(ab, "b");
assertTrue(abb.createNewFile());
chmod("600", abb);
File abx = new File(ab, "x");
assertTrue(abx.createNewFile());
chmod("u+x", abx);
// Run chmod recursive
chmodR(mode, a);
// Verify outcome
assertPermissions(a, "d" + expectedPermx);
assertPermissions(aa, "-" + expectedPerm);
assertPermissions(ab, "d" + expectedPermx);
assertPermissions(aba, "d" + expectedPermx);
assertPermissions(abb, "-" + expectedPerm);
assertPermissions(abx, "-" + expectedPermx);
assertTrue(FileUtil.fullyDelete(a));
}
@Test (timeout = 30000)
public void testBasicChmod() throws IOException {
requireWinutils();
// - Create a file.
// - Change mode to 377 so owner does not have read permission.
// - Verify the owner truly does not have the permissions to read.
File a = new File(TEST_DIR, "a");
a.createNewFile();
chmod("377", a);
try {
readFile(a);
assertFalse("readFile should have failed!", true);
} catch (IOException ex) {
LOG.info("Expected: Failed read from a file with permissions 377");
}
// restore permissions
chmod("700", a);
// - Create a file.
// - Change mode to 577 so owner does not have write permission.
// - Verify the owner truly does not have the permissions to write.
chmod("577", a);
try {
writeFile(a, "test");
fail("writeFile should have failed!");
} catch (IOException ex) {
LOG.info("Expected: Failed write to a file with permissions 577");
}
// restore permissions
chmod("700", a);
assertTrue(a.delete());
// - Copy WINUTILS to a new executable file, a.exe.
// - Change mode to 677 so owner does not have execute permission.
// - Verify the owner truly does not have the permissions to execute the file.
File winutilsFile = Shell.getWinUtilsFile();
File aExe = new File(TEST_DIR, "a.exe");
FileUtils.copyFile(winutilsFile, aExe);
chmod("677", aExe);
try {
Shell.execCommand(aExe.getCanonicalPath(), "ls");
fail("executing " + aExe + " should have failed!");
} catch (IOException ex) {
LOG.info("Expected: Failed to execute a file with permissions 677");
}
assertTrue(aExe.delete());
}
/** Validate behavior of chmod commands on directories on Windows. */
@Test (timeout = 30000)
public void testBasicChmodOnDir() throws IOException {
requireWinutils();
// Validate that listing a directory with no read permission fails
File a = new File(TEST_DIR, "a");
File b = new File(a, "b");
a.mkdirs();
assertTrue(b.createNewFile());
// Remove read permissions on directory a
chmod("300", a);
String[] files = a.list();
assertNull("Listing a directory without read permission should fail", files);
// restore permissions
chmod("700", a);
// validate that the directory can be listed now
files = a.list();
assertEquals("b", files[0]);
// Remove write permissions on the directory and validate the
// behavior for adding, deleting and renaming files
chmod("500", a);
File c = new File(a, "c");
try {
// Adding a new file will fail as expected because the
// FILE_WRITE_DATA/FILE_ADD_FILE privilege is denied on
// the dir.
c.createNewFile();
fail("writeFile should have failed!");
} catch (IOException ex) {
LOG.info("Expected: Failed to create a file when directory "
+ "permissions are 577");
}
// Deleting a file will succeed even if write permissions are not present
// on the parent dir. Check the following link for additional details:
// http://support.microsoft.com/kb/238018
assertTrue("Special behavior: deleting a file will succeed on Windows "
+ "even if a user does not have write permissions on the parent dir",
b.delete());
assertFalse("Renaming a file should fail on the dir where a user does "
+ "not have write permissions", b.renameTo(new File(a, "d")));
// restore permissions
chmod("700", a);
// Make sure adding new files and rename succeeds now
assertTrue(c.createNewFile());
File d = new File(a, "d");
assertTrue(c.renameTo(d));
// at this point in the test, d is the only remaining file in directory a
// Removing execute permissions does not have the same behavior on
// Windows as on Linux. Adding, renaming, deleting and listing files
// will still succeed. Windows default behavior is to bypass directory
// traverse checking (BYPASS_TRAVERSE_CHECKING privilege) for all users.
// See the following link for additional details:
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa364399(v=vs.85).aspx
chmod("600", a);
// validate directory listing
files = a.list();
assertEquals("d", files[0]);
// validate delete
assertTrue(d.delete());
// validate add
File e = new File(a, "e");
assertTrue(e.createNewFile());
// validate rename
assertTrue(e.renameTo(new File(a, "f")));
// restore permissions
chmod("700", a);
}
@Test (timeout = 30000)
public void testChmod() throws IOException {
requireWinutils();
testChmodInternal("7", "-------rwx");
testChmodInternal("70", "----rwx---");
testChmodInternal("u-x,g+r,o=g", "-rw-r--r--");
testChmodInternal("u-x,g+rw", "-rw-rw----");
testChmodInternal("u-x,g+rwx-x,o=u", "-rw-rw-rw-");
testChmodInternal("+", "-rwx------");
// Recursive chmod tests
testChmodInternalR("755", "rwxr-xr-x", "rwxr-xr-x");
testChmodInternalR("u-x,g+r,o=g", "rw-r--r--", "rw-r--r--");
testChmodInternalR("u-x,g+rw", "rw-rw----", "rw-rw----");
testChmodInternalR("u-x,g+rwx-x,o=u", "rw-rw-rw-", "rw-rw-rw-");
testChmodInternalR("a+rX", "rw-r--r--", "rwxr-xr-x");
// Test a new file created in a chmod'ed directory has expected permission
testNewFileChmodInternal("-rwxr-xr-x");
}
private void chown(String userGroup, File file) throws IOException {
Shell.execCommand(
winutils, "chown", userGroup, file.getCanonicalPath());
}
private void assertOwners(File file, String expectedUser,
String expectedGroup) throws IOException {
String [] args = lsF(file).trim().split("[\\|]");
assertEquals(StringUtils.toLowerCase(expectedUser),
StringUtils.toLowerCase(args[2]));
assertEquals(StringUtils.toLowerCase(expectedGroup),
StringUtils.toLowerCase(args[3]));
}
@Test (timeout = 30000)
public void testChown() throws IOException {
requireWinutils();
File a = new File(TEST_DIR, "a");
assertTrue(a.createNewFile());
String username = System.getProperty("user.name");
// username including the domain aka DOMAIN\\user
String qualifiedUsername = Shell.execCommand("whoami").trim();
String admins = "Administrators";
String qualifiedAdmins = "BUILTIN\\Administrators";
chown(username + ":" + admins, a);
assertOwners(a, qualifiedUsername, qualifiedAdmins);
chown(username, a);
chown(":" + admins, a);
assertOwners(a, qualifiedUsername, qualifiedAdmins);
chown(":" + admins, a);
chown(username + ":", a);
assertOwners(a, qualifiedUsername, qualifiedAdmins);
assertTrue(a.delete());
assertFalse(a.exists());
}
@Test (timeout = 30000)
public void testSymlinkRejectsForwardSlashesInLink() throws IOException {
requireWinutils();
File newFile = new File(TEST_DIR, "file");
assertTrue(newFile.createNewFile());
String target = newFile.getPath();
String link = new File(TEST_DIR, "link").getPath().replaceAll("\\\\", "/");
try {
Shell.execCommand(winutils, "symlink", link, target);
fail(String.format("did not receive expected failure creating symlink "
+ "with forward slashes in link: link = %s, target = %s", link, target));
} catch (IOException e) {
LOG.info(
"Expected: Failed to create symlink with forward slashes in target");
}
}
@Test (timeout = 30000)
public void testSymlinkRejectsForwardSlashesInTarget() throws IOException {
requireWinutils();
File newFile = new File(TEST_DIR, "file");
assertTrue(newFile.createNewFile());
String target = newFile.getPath().replaceAll("\\\\", "/");
String link = new File(TEST_DIR, "link").getPath();
try {
Shell.execCommand(winutils, "symlink", link, target);
fail(String.format("did not receive expected failure creating symlink "
+ "with forward slashes in target: link = %s, target = %s", link, target));
} catch (IOException e) {
LOG.info(
"Expected: Failed to create symlink with forward slashes in target");
}
}
@Test (timeout = 30000)
public void testReadLink() throws IOException {
requireWinutils();
// Create TEST_DIR\dir1\file1.txt
//
File dir1 = new File(TEST_DIR, "dir1");
assertTrue(dir1.mkdirs());
File file1 = new File(dir1, "file1.txt");
assertTrue(file1.createNewFile());
File dirLink = new File(TEST_DIR, "dlink");
File fileLink = new File(TEST_DIR, "flink");
// Next create a directory symlink to dir1 and a file
// symlink to file1.txt.
//
Shell.execCommand(
winutils, "symlink", dirLink.toString(), dir1.toString());
Shell.execCommand(
winutils, "symlink", fileLink.toString(), file1.toString());
// Read back the two links and ensure we get what we expected.
//
String readLinkOutput = Shell.execCommand(winutils,
"readlink",
dirLink.toString());
assertThat(readLinkOutput, equalTo(dir1.toString()));
readLinkOutput = Shell.execCommand(winutils,
"readlink",
fileLink.toString());
assertThat(readLinkOutput, equalTo(file1.toString()));
// Try a few invalid inputs and verify we get an ExitCodeException for each.
//
try {
// No link name specified.
//
Shell.execCommand(winutils, "readlink", "");
fail("Failed to get Shell.ExitCodeException when reading bad symlink");
} catch (Shell.ExitCodeException ece) {
assertThat(ece.getExitCode(), is(1));
}
try {
// Bad link name.
//
Shell.execCommand(winutils, "readlink", "ThereIsNoSuchLink");
fail("Failed to get Shell.ExitCodeException when reading bad symlink");
} catch (Shell.ExitCodeException ece) {
assertThat(ece.getExitCode(), is(1));
}
try {
// Non-symlink directory target.
//
Shell.execCommand(winutils, "readlink", dir1.toString());
fail("Failed to get Shell.ExitCodeException when reading bad symlink");
} catch (Shell.ExitCodeException ece) {
assertThat(ece.getExitCode(), is(1));
}
try {
// Non-symlink file target.
//
Shell.execCommand(winutils, "readlink", file1.toString());
fail("Failed to get Shell.ExitCodeException when reading bad symlink");
} catch (Shell.ExitCodeException ece) {
assertThat(ece.getExitCode(), is(1));
}
try {
// Too many parameters.
//
Shell.execCommand(winutils, "readlink", "a", "b");
fail("Failed to get Shell.ExitCodeException with bad parameters");
} catch (Shell.ExitCodeException ece) {
assertThat(ece.getExitCode(), is(1));
}
}
@SuppressWarnings("deprecation")
@Test(timeout=10000)
public void testTaskCreate() throws IOException {
requireWinutils();
File batch = new File(TEST_DIR, "testTaskCreate.cmd");
File proof = new File(TEST_DIR, "testTaskCreate.out");
FileWriter fw = new FileWriter(batch);
String testNumber = String.format("%f", Math.random());
fw.write(String.format("echo %s > \"%s\"", testNumber, proof.getAbsolutePath()));
fw.close();
assertFalse(proof.exists());
Shell.execCommand(winutils, "task", "create", "testTaskCreate" + testNumber,
batch.getAbsolutePath());
assertTrue(proof.exists());
String outNumber = FileUtils.readFileToString(proof);
assertThat(outNumber, containsString(testNumber));
}
@Test (timeout = 30000)
public void testTaskCreateWithLimits() throws IOException {
requireWinutils();
// Generate a unique job id
String jobId = String.format("%f", Math.random());
// Run a task without any options
String out = Shell.execCommand(winutils, "task", "create",
"job" + jobId, "cmd /c echo job" + jobId);
assertTrue(out.trim().equals("job" + jobId));
// Run a task without any limits
jobId = String.format("%f", Math.random());
out = Shell.execCommand(winutils, "task", "create", "-c", "-1", "-m",
"-1", "job" + jobId, "cmd /c echo job" + jobId);
assertTrue(out.trim().equals("job" + jobId));
// Run a task with limits (128MB should be enough for a cmd)
jobId = String.format("%f", Math.random());
out = Shell.execCommand(winutils, "task", "create", "-c", "10000", "-m",
"128", "job" + jobId, "cmd /c echo job" + jobId);
assertTrue(out.trim().equals("job" + jobId));
// Run a task without enough memory
try {
jobId = String.format("%f", Math.random());
out = Shell.execCommand(winutils, "task", "create", "-m", "128", "job"
+ jobId, "java -Xmx256m -version");
fail("Failed to get Shell.ExitCodeException with insufficient memory");
} catch (Shell.ExitCodeException ece) {
assertThat(ece.getExitCode(), is(1));
}
// Run tasks with wrong parameters
//
try {
jobId = String.format("%f", Math.random());
Shell.execCommand(winutils, "task", "create", "-c", "-1", "-m",
"-1", "foo", "job" + jobId, "cmd /c echo job" + jobId);
fail("Failed to get Shell.ExitCodeException with bad parameters");
} catch (Shell.ExitCodeException ece) {
assertThat(ece.getExitCode(), is(1639));
}
try {
jobId = String.format("%f", Math.random());
Shell.execCommand(winutils, "task", "create", "-c", "-m", "-1",
"job" + jobId, "cmd /c echo job" + jobId);
fail("Failed to get Shell.ExitCodeException with bad parameters");
} catch (Shell.ExitCodeException ece) {
assertThat(ece.getExitCode(), is(1639));
}
try {
jobId = String.format("%f", Math.random());
Shell.execCommand(winutils, "task", "create", "-c", "foo",
"job" + jobId, "cmd /c echo job" + jobId);
fail("Failed to get Shell.ExitCodeException with bad parameters");
} catch (Shell.ExitCodeException ece) {
assertThat(ece.getExitCode(), is(1639));
}
}
}