How-To: Debug HDFS applications in Eclipse

I started using the HDFS API in Java recently in order to port some legacy applications over to HDFS. One thing that I noticed is that when running the application via “hadoop jar” it properly accessed HDFS and stored its files there but if I ran it in the debugger the API calls succeeded but the files never showed up.

After a bit more investigation I saw that the HDFS API was unable to read my configuration files and find the NameNode so it defaulted to writing the files on the local file system instead. This is nice behavior for debugging sometimes but can be dangerous if you’re running an application that must put its files in HDFS like a mission critical application that doesn’t fulfill its operational contract if data is lost. In the case of an application like that accidentally writing to the local file system could be disastrous and expensive so it’s good to know how to detect when this happens, and/or overcome it in a situation where you’re trying to debug against your HDFS cluster.

Let’s look at a simple code snippet that connects to HDFS that is just a cleaned up version of Yahoo’s Hadoop tutorial:

import java.io.IOException;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;

public class TestClass {
	private static final String theFilename = "timmattison.txt";
	private static final String message = "This is the message that gets put into the file";

	public static void main(String[] args) {
		try {
			// Get the configuration and connect to the file system
			Configuration conf = new Configuration();
			FileSystem fs = FileSystem.get(conf);

			// Create the path object for our output file
			Path filenamePath = new Path(theFilename);

			// Does it exist already?
			if (fs.exists(filenamePath)) {
				// Yes, remove it first
				fs.delete(filenamePath);
			}

			// Create the output file and write the data into it
			FSDataOutputStream out = fs.create(filenamePath);
			out.writeUTF(message);
			out.close();

			// Open the output file as an input file and read it
			FSDataInputStream in = fs.open(filenamePath);
			String messageIn = in.readUTF();

			// Print its contents and close the file
			System.out.print(messageIn);
			in.close();
		} catch (IOException ioe) {
			System.err.println("IOException during operation: "
					+ ioe.toString());
			System.exit(1);
		}
	}
}

If you run this code with “hadoop jar” you’ll see that it creates the expected file (timmattison.txt) in the current user’s default path in HDFS. If you run this code with Eclipse either in Run or Debug mode you’ll see that the file is not created in HDFS, it is created relative to where Eclipse starts the JVM for the new process.

We can tell where the HDFS library will attempt to write our files by very simply checking the type of the FileSystem object that is created by the call to FileSystem.get(conf). If that object’s type is LocalFileSystem we are not connecting to HDFS. However if that object’s type is DistributedFileSystem then you know that you’re connected to a Hadoop cluster and writing to a real instance of HDFS.

In your code you can leverage this in a few ways. First, if you always need to be sure you’re writing to the cluster you can check the fs variable and see if it is an instance of LocalFileSystem. If it is you can signal an error, e-mail an admin, etc. Configuration changes in the field could cause this to happen so it is important to be aware of. In general running programs through “hadoop jar” will make sure this doesn’t happen but a little defensive programming usually can’t hurt. Just consider what the cost of running your code against the wrong file system would be and trap this condition accordingly.

If you’re interested in handling this automatically in your development environment I’ve come up with a simple pattern that works for me. In some instances such as running your code outside of Eclipse without “hadoop jar” this pattern could fail so only use it specifically for debugging in Eclipse. Here’s what I do:

import java.io.IOException;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.LocalFileSystem;

public class TestClass {
	private static final String CORE_SITE_NAME = "core-site.xml";
	private static final String CORE_SITE_LOCATION = "/etc/hadoop-0.20/conf.empty/"
			+ CORE_SITE_NAME;
	private static final String LOCAL_SEARCH_PATH = "bin/";
	private static final String LOCAL_CORE_SITE_LOCATION = LOCAL_SEARCH_PATH
			+ CORE_SITE_NAME;

	private static boolean updatedConfiguration = false;

	public static void main(String[] args) throws IOException {
		try {
			// Get the configuration and connect to the file system
			Configuration conf = new Configuration();
			FileSystem fs = FileSystem.get(conf);

			// Is this the local file system?
			if (fs instanceof LocalFileSystem) {
				// Yes, we need to do use the cluster. Update the configuration.
				updatedConfiguration = true;

				/**
				 * Remove the file if it already exists. Just in case this is a
				 * symlink or something.
				 */
				removeTemporaryConfigurationFile();

				// Copy the core-site.xml file to where our JVM can see it
				copyConfigurationToTemporaryLocation();

				// Recreate the configuration object
				conf = new Configuration();

				// Get a new file system object
				fs = FileSystem.get(conf);

				// Is this the local file system?
				if (fs instanceof LocalFileSystem) {
					// Yes, give up. We cannot connect to the cluster.
					System.err.println("Failed to connect to the cluster.");
					System.exit(2);
				}
			}

			// Do your HDFS related work here...
		} catch (IOException ioe) {
			// An IOException occurred, give up
			System.err.println("IOException during operation: "
					+ ioe.toString());
			System.exit(1);
		} finally {
			// Did we update the configuration?
			if (updatedConfiguration) {
				// Yes, clean up the temporary configuration file
				removeTemporaryConfigurationFile();
			}
		}
	}

	private static void copyConfigurationToTemporaryLocation()
			throws IOException {
		Runtime.getRuntime().exec(
				new String[] { "cp", CORE_SITE_LOCATION,
						LOCAL_CORE_SITE_LOCATION });
	}

	private static void removeTemporaryConfigurationFile() throws IOException {
		Runtime.getRuntime().exec(
				new String[] { "rm", LOCAL_CORE_SITE_LOCATION });
	}
}

Now where it says “Do your HDFS related work here…” you can put your code and be sure that it’s accessing the cluster, not your local file system.

In a future article and on github I’ll wrap this up in a reusable chunk so that you won’t have to copy and paste this every time you start a new project.

How-To: Install Google’s Android Eclipse plugin (and/or adb) on 64-bit Debian/Ubuntu

Today I had to reinstall the Android plugin on my system and I recently upgraded to a 64-bit development VM. To my surprise the installation didn’t go smoothly at all. After restarting Eclipse twice I was constantly presented with two error messages “Failed to parse the output of ‘adb version’” and “adb: error while loading shared libraries: libncurses.so.5: cannot open shared object file: No such file or directory”. Your system may also present another error message that reads “adb: error while loading shared libraries: libstdc++.so.6: cannot open shared object file: No such file or directory”.

I could see that it was looking for libncurses.so.5 however I know that ncurses is already installed on my machine in /lib as /lib/libncurses.so.5. So where exactly was Eclipse/adb looking for it? It turns out that it wants to find its libraries in the /lib32 directory but you can’t just symlink it or you’ll get an error that reads “wrong ELF class: ELFCLASS64″. adb needs to have the 32-bit versions installed or it won’t function at all.

So to get up and running just run the following command to fix the issue:

sudo apt-get install lib32ncurses5 lib32stdc++6

After that just restart Eclipse and the issue should be fully put to bed. Let me know how it works out for you or if you run into trouble.

If you still run into trouble like an error message that reads “aapt: error while loading shared libraries: libz.so.1: cannot open shared object file: No such file or directory” you need to install the ia32-libs like this:

sudo apt-get install ia32-libs

Then rebuild your project and the errors should be gone.

UPDATE 2012-02-14: Rortian reports that the following command words on Fedora 16:

yum install ncurses-libs.i686 libstdc++.i686 libgcc.i686

How-To: Fix “This selection is not within a valid module” errors in Eclipse when refactoring web projects

After rebuilding my Eclipse environment I had the need to refactor one of my web projects. Once I refactored it using the usual Eclipse method (right-click on the project -> Refactor -> Rename) I wasn’t able to start the project on my web server. The error message I got was

This selection is not within a valid module

Here are the steps to resolve this issue:

Step 1: Select the “Servers” tab in Eclipse

Step 2: Right-click on your server and select “Delete”

Step 3: If prompted, make sure all options are checked (“Delete unused server configuration(s)”, “Delete running server(s)”, and “Stop server(s) before deleting”) and click OK

Step 4: Close Eclipse

Step 5: Go into your project’s .settings directory and edit the org.eclipse.wst.common.component file

Step 6: Change the following values to the name of your refactored project:

wb-module deploy-name
property name="context-root" value

Step 7: Restart Eclipse and re-run your project

Now your project should deploy to your servlet container properly. Any time you refactor a web project in Eclipse you may need to do this so keep these instructions around if you think you’ll need to do it often.

How-To: Fix a broken Eclipse environment without starting completely from scratch

Over time my Eclipse environment has become quiet a bit crufty. I have a bunch of errors from plugins that I installed that didn’t work properly and yesterday my environment completely stopped building my Java projects. When I tried to export a JAR I got these two messages:

JAR creation failed. See details for additional information.

class file(s) on classpath not found or not accessible

There are also some other errors you may see that indicate you should probahly rebuild your environment:

No property tester contributes a property projectPersistentProperty org.eclipse.team.core.repository to type class org.eclipse.core.internal.resources.Project

org.eclipse.core.runtime.CoreException: No property tester contributes a property projectPersistentProperty org.eclipse.team.core.repository to type class org.eclipse.core.internal.resources.Project

java.util.zip.ZipException: error in opening zip file

An internal error occurred during: "Initializing Java Tooling"

An error occurred while loading the manifest

In my case the class files that it couldn’t find were classes that Eclipse was supposed to build. After checking everything I couldn’t figure it out so I decided that it was time to start fresh. What I wanted to do was nuke the whole environment but not have to hunt around for all the JARs that I had in my user-defined libraries. In the future I’ll put all of those files in a directory for each library but right now I needed a fix so I could get back to work.

Here are the steps to get a fresh start without reinstalling and without losing your user libraries:

  • Step 1: Quit Eclipse
  • Step 2: Make a backup of your workspace
  • Step 3: Go into your workspace on the command-line and run
    mv .metadata .metadata-old
  • Step 4: Restart Eclipse. Don’t panic! Everything is only gone temporarily. This step forces Eclipse to build a fresh .metadata directory.
  • Step 5: Quit Eclipse
  • Step 6: Restore your user-defined libraries on the command-line by running
    cp .metadata-old/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.jdt.core.prefs .metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.jdt.core.prefs
  • Step 7: Restart Eclipse and import your projects back into the workspace

If you are still having trouble and cannot build your code or export JARs make sure you right-click on a project that is having that problem, select “Java Build Path”, then select “Libraries”. Any libraries marked with a red X are almost certainly your issue. Resolve this issue (or remove them if they’re not needed) and you’ll be almost right where you left off but with a fresh, clean environment.

You will lose settings like the location of your Android SDK if you use that, or the location where the save dialogs open up, but if you can deal with that this is a great way to start over. Once you’re comfortable with your setup you can remove the old metadata by running this on the command-line

rm -rf .metadata-old

Don’t delete your backup copy though! You never know when you’ll need it.

How-To: Fix “[ERROR] Unexpected java.lang.NoSuchMethodError” messages when compiling GWT code in Eclipse

This morning when I opened up an old GWT project to rebuild it and bring it from GWT 1.5 to GWT 1.7 Eclipse spit out this error message:

[ERROR] Unexpected

java.lang.NoSuchMethodError: org.eclipse.jdt.internal.compiler.lookup.ProblemReferenceBinding.closestReferenceMatch()Lorg/eclipse/jdt/internal/compiler/lookup/ReferenceBinding;
	at com.google.gwt.dev.javac.JsniChecker$CheckingVisitor.findClass(JsniChecker.java:231)
	at com.google.gwt.dev.javac.JsniChecker$CheckingVisitor.checkRefs(JsniChecker.java:142)
	at com.google.gwt.dev.javac.JsniChecker$CheckingVisitor.endVisit(JsniChecker.java:65)
	at org.eclipse.jdt.internal.compiler.ast.MethodDeclaration.traverse(MethodDeclaration.java:247)
	at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.traverse(TypeDeclaration.java:1337)
	at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.traverse(TypeDeclaration.java:1206)
	at org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration.traverse(CompilationUnitDeclaration.java:518)
	at com.google.gwt.dev.javac.JsniChecker.check(JsniChecker.java:350)
	at com.google.gwt.dev.javac.JsniChecker.check(JsniChecker.java:340)
	at com.google.gwt.dev.javac.CompilationUnitInvalidator.validateCompilationUnits(CompilationUnitInvalidator.java:159)
	at com.google.gwt.dev.javac.CompilationState.compile(CompilationState.java:198)
	at com.google.gwt.dev.javac.CompilationState.refresh(CompilationState.java:178)
	at com.google.gwt.dev.javac.CompilationState.(CompilationState.java:93)
	at com.google.gwt.dev.cfg.ModuleDef.getCompilationState(ModuleDef.java:264)
	at com.google.gwt.dev.Precompile.precompile(Precompile.java:283)
	at com.google.gwt.dev.Compiler.run(Compiler.java:170)
	at com.google.gwt.dev.Compiler$1.run(Compiler.java:124)
	at com.google.gwt.dev.CompileTaskRunner.doRun(CompileTaskRunner.java:88)
	at com.google.gwt.dev.CompileTaskRunner.runWithAppropriateLogger(CompileTaskRunner.java:82)
	at com.google.gwt.dev.Compiler.main(Compiler.java:131)

As usual I Googled around a bit and found a solution but I really wanted to know exactly how to fix this issue in the future. The solution is even simpler than the original poster thought but without their input I would’ve never figured it out so thank you sujaydutta!

The issue stems from the GWT compiler needing your classpath in a certain order. In particular you need to have your application source as the first entry, then GWT as the second entry. In Eclipse you can do it like this:

  • Step 1: Right-click on your project and select “Properties”
  • Step 2: Click on “Java Build Path”
  • Step 3: Click on the “Order and Export” tab
  • Step 4: Click on your “GWT SDK” entry (if you’re using the Google Plugin for Eclipse it’s called “GWT SDK”, otherwise it’s whatever you decided to name it when you created your custom user library)
  • Step 5: Click the “Top” button on the right
  • Step 6: Click on your “src” entry (it should be your project name, followed by a forward slash, and then the word “src”)
  • Step 7: Click the “Top” button on the right
  • Step 8: Click “OK”

Now when you go to rebuild your project you’ll finally be rid of this error message and you can start hacking out GWT 1.7 code. Good luck! Post below if the above instructions don’t work for you and maybe I can help.

Mobile Analytics by Mixpanel