2010-12-29

Howto make your private VM Cluster, Part III

Continuing with my saga, next up is DRBD. I'm using DRBD because I also want to test it as a viable alternative for network raid.

To use DRBD I first created a LVM volume to use:
lvcreate -n drbd-demo -L 100M internal-hd
Then I configured DRBD on both nodes, fortunately gentoo simplifies a great part of the process (you have to do this on both nodes):
cd /etc
cp /usr/share/doc/drbd-*/drbd.conf.bz2 .
bunzip2 drbd.conf.bz2
Then I created a wwwdata resource by first configuring it (again on both nodes). This is done by creating a file /etc/drbd.d/wwwdata.res with the contents:
resource wwwdata {
    meta-disk internal;
    device    /dev/drbd1;
    syncer {
        verify-alg sha1;
    }
    net {
        allow-two-primaries;
    }
    on node1 {
        disk    /dev/mapper/internalhd-drbd--demo;
        address 192.168.100.10:7789;
    }
    on node2 {
        disk    /dev/mapper/internalhd-drbd--demo;
        address 192.168.100.11:7789;
    }
}
I added the drbd module to the /etc/modules.autoload.d/kernel-2.6 on both nodes.

Finally I started drbd on node 1 as follows:
drbdadm create-md www-data
modprobe drbd
drbdadm up wwwdata
And on node 2 as follows:
drbdadm --force create-md www-data
modprobe drbd
drbdadm up wwwdata
I then used node 1 as reference for the data:
drbdadm -- --overwrite-data-of-peer primary wwwdata
I monitored the sync process until it was completed with:
watch cat /proc/drbd
When completed I created the file system and populated it with an index.html file indicating the cluster:
mkfs.ext4 /dev/drbd1
mount /dev/drbd1 /mnt
# Create a index.html
umount /dev/drbd1
I configured the cluster to use drbd as follows (this will enter the crm shell but don't panic):
crm
cib new drbd
configure primitive WebData ocf:linbit:drbd params drbd_resource=wwwdata op monitor interval=30s
configure ms WebDataClone WebData meta master-max=1 master-node-max=1 clone-max=2 clone-node-max=1 notify=true
cib commit drbd
quit
After this I configured a WebFS service so the lighttpd will serve from the DRBD mounted volume.
crm
cib new webfs
configure primitive WebFS ocf:heartbeat:Filesystem params device="/dev/drbd/by-res/wwwdata" directory="/var/www/localhost/htdocs" fstype="ext4"
configure colocation WebFS-on-WebData inf: WebFS WebDataClone:Master
configure order WebFS-after-WebData inf: WebDataClone:promote WebFS:start
configure colocation WebSite-with-WebFS inf: WebSite WebFS
configure order WebSite-after-WebFS inf: WebFS WebSite
cib commit webfs
quit
After this, if you go to your cluster web page (http://192.168.100.20) you will see the contents of the index.html that you created for the cluster.

You can use "crm_mon" to monitor the cluster and look at /var/log/message to view error messages. To simulate a full service relocation go the the node were the services are running and issue "crm node standby" this will put the current node on standby forcing the services to be moved to the other node. After that you can do "crm node online" to bring the node back online.

This concludes this series. Maybe I'll put up another on to have nfs use the drbd, depends on free time.

Howto make your private VM Cluster, Part II

In the previous entry I showed how to build the basic structure for your own private VM Cluster. Today I'm going to show you how to create a cluster with two VMs that provides High-Availability for a WebServer using DRBD to replicate the Web Site.

First you need to create a virtual machine. I decided to create a VM with Gentoo. I will use LVM to keep the partition to use for drbd small since this is a simple test.

I created a qemu-img for a base gentoo installation (my goal is to install gentoo on the VM and then reuse it as base for the other VMs). To create the image just run:
qemu-img create -f qcow2 gentoo.qcow2 10G
I started the VM using that image and followed Gentoo's installation guide. My partition scheme was 100Mb (boot), 512Mb (swap), 5Gb (root), rest for lvm.

I used the gentoo-sources, configuring all virtio devices, drbd and the device-mapper. I configured genkernel to use lvm so it detects the lvm volumes at boot. I used grub and added all the genkernel options.

Remember that if you use the minimal installation CD the hard disk will be called sda but after setting up virtio in the kernel it will be called vda. I created a generic startvm script that will provide the disk and network card using virtio. I called it startVM:
#!/bin/bash
set -x

if [[ $# != 2 && $# != 3 ]]; then
    echo "Usage: startVM <mac> <hda> [cdrom]"
    exit 1
fi

# Get the location of the scriptSCRIPT=`readlink -f $0`
SCRIPT_PATH=`dirname $SCRIPT`

# Create tap interface so that the script /etc/qemu-ifup can bridge it# before qemu startsUSERID=`whoami`
IFACE=`sudo tunctl -b -u $USERID`

# Setup KVM parametersCPUS="-smp 8"
MEMORY="-m 1G"
MACADDRESS=$1
HDA=$2
if [[ $# == 3 ]]; then
    CDROM="-cdrom $3"
else
    CDROM=""
fi
NET="-net nic -net tap,script=/etc/qemu-ifup"

# Start kvmkvm $CPUS $MEMORY -drive file=$HDA,if=virtio,boot=on $CDROM -net nic,model=virtio,macaddr=$MACADDRESS -net tap,ifname=$IFACE,script=$SCRIPT_PATH/qemu-ifup

# kvm has stopped - remove tap tap interfacesudo tunctl -d $IFACE &> /dev/null
After successfully booting to the Gentoo VM I halted the VM to create another disk image. I may want to reuse this vanilla Gentoo VM in the future so I created another disk image taking this vanilla one as base as follows:
qemu-img create -f qcow2 -o backing_file=gentoo.qcow2 gentoo-drbd.qcow2
Then I started the VM with the new script with:
startVM DE:AD:BE:EF:E3:1D gentoo-drbd.qcow2
The MAC Address will be important later.

Now I installed all the packages that I will be needing for each node in the cluster. The goal is to avoid having to build them many times and to reuse this image for all nodes. Basic steps are as follows:
emerge -av telnet-bsd drbd lvm2 pacemaker lighttpd
eselect python set python2.6
pvcreate /dev/vda4
vgcreate internalhd /dev/vda4
I changed python to 2.6 because pacemaker requires it. I added the following to /etc/hosts to avoid doing it everyware:
192.168.100.10 node1
192.168.100.11 node2
192.168.100.20 cluster
Next I created an image for the two nodes as follows:
qemu-img create -f qcow2 -o backing_file=gentoo-drbd.qcow2 gentoo-drbd-node1.qcow2
cp gentoo-drbd-node1.qcow2 gentoo-drbd-node2.qcow2
Then I created to scripts, one to start each VM. The contents are as follows (for node2 you must change the mac address):
#!/bin/bash
set -x

# Get the location of the scriptSCRIPT=`readlink -f $0`
SCRIPT_PATH=`dirname $SCRIPT`

startVM DE:AD:BE:EF:E3:1D gentoo-drbd-node1.qcow2
I then configured static IPs for each node, that is: edit /etc/conf.d/hostname to be either node1 or node2 and the contents of /etc/conf.d/net to be:
config_eth0=( "192.168.100.10/24" )
routes_eth0=( "default via 192.168.100.1" )
For node 2 the IP ends in 11. Next I confired corosync. This must be done on both nodes:
cd /etc/corosync
cp corosync.conf.example to corosync.conf
Edit the corosync.conf file and make the bindnetaddr be the IP address of the node. And add the pacemaker service by adding the following to the end of the file:
service {
   name: pacemaker
   ver: 0
}
I started corosync on both nodes and marked it to start on boot:
/etc/init.d/corosync start
rc-update add corosync default
Then I proceded to congiure the cluster. First I turned of STONITH:
crm configure property stonith-enabled=false
Then I created the Virtual IP for the Cluster:
crm configure primitive ClusterIP ocf:hertbeat:IPaddr2 params ip=192.168.100.20 cidr_netmask=32 op monitor interval=30s
Marked the cluster to runt with two nodes (without quorom, please don't discuss this in the comments) and for resource stickiness (to avoid having the resources move around if not needed):
crm configure property no-quorum-policy=ignore
crm configure rsc_defaults resource-stickiness=100
I added lighttpd as a service. First I created index.html on both nodes and different so that I can check if things are working. Next I created the service in the cluster:
crm configure primtive WebSite lsb:lighttpd op monitor interval=30s
crm configure colocation website-with-ip INFINITY: WebSite ClusterIP
crm configure order lighttpd-after-ip mandatory: ClusterIP WebSite

You can test and access the cluster address (http://192.168.100.20) to see whose answering. You can stop the corosync service to view the service migrate between nodes.

Next up DRBD.

2010-12-28

Howto make your private VM Cluster, Part I

I wanted to make some experiments with DRBD, pacemaker and others. The goal is to test a few configurations for an high availability scenario. Since I don't want to make changes to my machine the solution is to use Virtual Machines. I decided to go all open source and use KVM. Since I want more that a single VM I decided to setup a private bridge to which all the VMs would connect. The bridge would provide DHCP and DNS services and NAT + Firewall to the internet.

I use Gentoo. If you use another distribution or firewall tool you should adapt these instructions and scripts to fit your needs.

First I created a script that sets up my bridge. The script is as follows (I called it setupBridge):
#!/bin/bash
set -x

# Setup the bridge
sudo brctl addbr br0
sudo ifconfig br0 192.168.100.1 netmask 255.255.255.0 up

# Launch DNS and DHCP Server on br0
sudo dnsmasq -q -a 192.168.100.1 --dhcp-range=192.168.100.50,192.168.100.150,forever --pid-file=/tmp/br0-dnsmasq.pid

# Launch updated firewall configuration
sudo firehol /home/nsousa/KVM/firehol-br0.conf start
The bridge is created and I give it the 192.168.100.1 ip address. I use dnsmasq to provide DNS and DHCP. The DHCP provided addresses will be from 192.168.100.50 to 150. Finally I adapt the firewall using firehol. Here is the firehold configuration file (firehol-br0.conf):
version 5

# Allow all traffic in the Bridge
interface br0 bridge
server all accept
client all accept

# Accept all client traffic on any interface
interface any world
client all accept

# NAT to the internet
router bridge2internet inface eth0 outface br0
masquerade reverse
client all accept

# Bridge Routing
router bridge-routing inface br0 outface br0
server all accept
client all accept
This basically allows the VMs to connect to any service running on br0 (the host). It allows any one to connect to anyone as client (so the host can go to the internet). Finally it sets up NAT using masquerade so the VMs can use the bridge to access the internet, but as clients only.

To undo all these changes I have a script that tears all this setup down. I called it teardownBridge and here are its contents:
#!/bin/bash
set -x

# Stop DHCP and DNS Server
sudo kill -15 `cat /tmp/br0-dnsmasq.pid`
sudo rm /tmp/br0-dnsmasq.pid

# Stop the bridge
sudo ifconfig br0 down
sudo brctl delbr br0

# Reset the firewall
sudo firehol /etc/firehol/firehol.conf start
Before moving to the script that starts one VM I first need to show the script that sets up the interface for qemu to use (I called it qemu-ifup):
#!/bin/bash
set -x

if test $(/sbin/ifconfig | grep -c $1) -gt 0; then
sudo /sbin/brctl delif br0 $1
sudo /sbin/ifconfig $1 down
fi

sudo /sbin/ifconfig $1 0.0.0.0 promisc up
sudo /sbin/brctl addif br0 $1
The goal here is to add the tap device created to the bridge. I first remove it if it is already there.

Last but not least the script that starts a VM. This script sets up a tap device for the current user, configures KVM start-up parameters and starts KVM. When KVM ends it removes the tap device to keep the system clean. Here is the contents of the script adapted to use a hard disk and the minimal install cd for gentoo (I called it startVM):
#!/bin/bash
set -x

# Create tap interface so that the script /etc/qemu-ifup can bridge it
# before qemu starts
USERID=`whoami`
IFACE=`sudo tunctl -b -u $USERID`

# Setup KVM parameters
MEMORY="-m 512"
IMGPATH="/home/nsousa/KVM"
IMAGE=gentoo.qcow2
CDROM="-cdrom /home/nsousa/Downloads/install-x86-minimal-20101123.iso"
MACADDRESS="DE:AD:BE:EF:5D:A3"
NET="-net nic -net tap,script=/etc/qemu-ifup"

# Start kvm
kvm $MEMORY -hda $IMGPATH/$IMAGE $CDROM -net nic,macaddr=$MACADDRESS -net tap,ifname=$IFACE,script=/home/nsousa/KVM/qemu-ifup

# kvm has stopped - remove tap tap interface
sudo tunctl -d $IFACE &> /dev/null
Next step is to make a minimal gentoo installation on a VM to use as base for all the VMs in the cluster. I'll probably refactor the startVM script to separate the common parts (the private parts will be the disk image to use and MAC Address).

Stay tuned for more updates.

2010-09-15

Laptop Power Saving

Today I had an issue updating the kernel on my company's laptop. I ended up reconfiguring it from strach istead of doing the oldconfig option (make oldconfig didn't ask anything but the kernel hard locked on boot). I took the oportunity to try to get a more duration out of the battery. How? By consuming less power.

PowerTop came in and lend a helping hand but it was not sufficient. When I enabled USB Suspend (CONFIG_USB_SUSPEND) the keyboard didn't work well. I have a keyboard, mouse and webcam connected to a hub that I use when I'm on my desk. Everything was working, power was being saving, even for the keyboard. The problem was that when the keyboard entered power saving mode and I pressed a key the first key that I pressed would be lost. The documentation talks about this issue but I didn't want to loose on the opportunity to increase my laptop's autonomy. Solution: udev to the rescue.

I created a udev rule that detects the USB Keyboard (by vendor and product id, maybe someday I'll connect to a keyboard without a problem). When the keyboard is connect it disables auto suspend just for the keyboard. Here is how I've done it (Note:I'm using Gentoo, but this should work on any linux, with more or less salt).

I created a udev rule in "/etc/udev/rules.d/92-usb-autosuspendfix.rules" with the following contents:
SUBSYSTEMS=="usb", ATTRS{idVendor}=="04f2", ATTRS{idProduct}=="0403",
RUN+="/etc/fixes/disableKeyBoardAutoSuspend.sh 04f2 0403"
The "/etc/fixes/disableKeyBoardAutoSuspend.sh" script does the whole magic. It search for the usb device in the sysfs and toggles the power control to on (it defaults to auto if the device supports power control).
#!/bin/bash

cd /sys/bus/usb/devices/
for device in `ls`; do
if [ -f $device/idVendor ]; then
if [ -f $device/idProduct ]; then
vendor=`cat $device/idVendor`
product=`cat $device/idProduct`
if [ "$1 $2" == "$vendor $product" ]; then
echo "Disable power for device $vendor:$product."
cd $device/power
echo on > level
fi
fi
fi
done
Hope this helps you. Maybe we should start a blacklist of USB devices that don't work well with power control.

2010-09-10

When hackers have fun!

Today I was building the RRDTool from source and I noticed the output of the configuration part was ordering a CD.
config.status: executing po-directories commands
config.status: creating po/POTFILES
config.status: creating po/Makefile
config.status: executing default commands
checking in... and out again
ordering CD from http://tobi.oetiker.ch/wish .... just kidding ;-)

----------------------------------------------------------------
Config is DONE!
This shows that hackers love to play ;-)

2010-05-08

A new Laptop

I just got a bran new Sony Vaio VPCF11Z1E as part of a compensation of making the b2b web site for my family's company. It comes preloaded with Windows 7 that I intend to leave there just in case. I'll put any special instruction on getting it running with Gentoo, my Linux distribution of choice, but first I like to talk about the hands on experience with such a computer.

I decided to turn it on and let Windows 7 do its thing. After all, it has 500Gb of disk space and the goal is to replace my old broken laptop that served me for almost 10 years and that one had a 80Gb hard disk. So, if I leave windows with 100 Gb of disk space it should be more than sufficient.

After turning on the PC I decided to follow the guideline and let windows and Sony's update tools run. It took an amazing 4 hours to download and install everything. I have an ADSL connection that could do 24Mbit/s but since I'm far away from the phone box it only does 8Mbit/s. What Amazed me is that just for Sony's updates it had to download almost 1Gb of data. I mean, it is a brand new laptop couldn't be updated already?

After all these updates I decided I should also create the recovery disks. You see, way back when I bought my old laptop it came with recovery disks, recovery disks and a whole lot more things. Today the laptops don't have recovery disks but recovery partitions. The disks you have to create them your self (another 2 ours to the mix) or to order and pay for them.

I call this a bad user experience. I brand new top of the line computer and I needed to go by 4 dvds and invest over 6 hours just to get it to a point that it is ready for usage if you follow the guidelines.

On top of that these people are getting more stupid every day. I was going to use windows disk management tool to shrink the windows partition to say 100Gb and I found out some nice gems. First the recovery partition is in the beginning of the disk. I though the faster part of the hard drive was its beginning, but I must be wrong, the Internet and all those that discuss there must also be wrong. You see, someone at Sony decided to put the recovery partition in the beginning of the drive. Strange because all laptops I saw until today had this partition at the end of the hard drive. To make things worse there is another 100Mb partition after that one and then comes the rest of the disk. What is the 100Mb partition used for? Well, it is called the System Rescue partition and it is the one with the boot flag. I assume this has the software Sony uses to recover the computer.

To culminate my disappointment windows is reporting that it can only shrink the partition to 244G. I'll try shrinking it, then defrag it and then shrinking it again. If it doesn't work it seems I'll be putting the recovery partition to the test because I'm going to delete the partition and use the recovery to get windows to use only 100G or so. I mean, it has 49Gb of used disk space and it is a clean install with updates. What the hell is this thing doing? And 49Gb just for the operating system with a browser, demo of Office an little more? That is more than bloat!

2010-04-13

GWT Part II: From Ant to Maven

In the last entry I showed how to get started with GWT and get a nicer build file with Ant. In this entry I'll explain the process of moving from Ant to Maven.

My goal here is to take advantage of all of Maven's features while still maintaining a workable project configuration that I can use from a command line and from Eclipse. GWT's tools generate an Eclipse project but don't do other things like, for instance, adding dependencies to the Eclipse project when you add them to the build file. Maybe you can do this using Apache's Ivy. I haven't tried it, maybe I will someday if I end up hitting a wall using Maven...

First thing to do is to rearrange our source code. Maven expects things to be on different paths and we will have to do that. I started by moving all java source code to a newly created "src/main/java". I'm using git so I just did:
$ mkdir -p src/main/java
$ git mv src/org src/main/java/org
I had to do a similar step for the unit tests:
$ mkdir -p src/test/java
$ git mv test/org src/test/java
$ rmdir test
Finally I had to move the war file to the folder maven expects the WAR resources to be int. It is also a simple move operation:
$ mkdir -p src/main/webapp
$ git mv war/* src/main/webapp/
$ rmdir war
$ git rm -f src/main/webapp/WEB-INF/lib/gwt-servlet.jar
I also removed the gwt-servlet.jar file that was added by the GWT tools as maven will take care of it for me.

Maven supports resources and resource filtering but GWT has special needs. This means that GWT's XML files for the source code need to reside on the same code as the java files, something that is not usual in Maven projects. But I had to do this for the test resources, that is, for the GWT xml file used in the tests. That was relatively simple:
$ mkdir -p src/test/resources/org/nuno/backoffice
$ git mv src/test/java/org/nuno/backoffice/BackOfficeJUnit.gwt.xml src/test/resources/org/nuno/backoffice
But this is not sufficient. The naming convention for unit tests are different. We want to support plain old unit tests and GWT Test Cases. For Maven the convention is to have your tests named "GwtTest*" for GWT based unit tests and ending with either "Test" or "TestCase" for plain old Unit Tests. Luckely this is a simple change. I renamed the "BackOfficeTest.java" file to "GwtBackOffice.java" with the following command:
$ git mv src/test/java/org/nuno/backoffice/client/BackOfficeTest.java src/test/java/org/nuno/backoffice/client/GwtTestBackOffice.java
Next I edited the file to change the name of the class. I will try to explain how to have 70% of your unit tests being plain old JUnit tests (without GWT) overhead in a future entry.

Since I'm want to use Eclipse's Maven plug-in I simply deleted the eclipse projects with "git rm .classpath .project". Now comes the hard part: the Project Object Model (POM).

Making a POM for a GWT project was a collection of trials and errors with many searches on Google, forums and others. I finally got a simple POM that works for all that I needed so far, that is, to build the project, run simple unit tests and GWT based tests, run in development mode, make unit and coverage reports and that can be used directly from Eclipse.

The GWT for Maven plug-in has support to generate the ASYNC interfaces automatically. I decided to use this support I went ahead and removed the Async version of the interface as follows:
$ git rm src/main/java/org/nuno/backoffice/client/GreetingServiceAsync.java
With all these steps and a lot of debugging, trial and error I ended up with this POM:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.nuno.backoffice</groupId>
<artifactId>BackOffice</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>Back Office</name>

<properties>
<gwt.version>2.0.3</gwt.version>
</properties>

<dependencies>
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-servlet</artifactId>
<version>${gwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-user</artifactId>
<version>${gwt.version}</version>
<scope>provided</scope>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
</dependencies>

<repositories>
<repository>
<id>gwt-maven</id>
<url>http://gwt-maven.googlecode.com/svn/trunk/mavenrepo/</url>
</repository>
</repositories>

<pluginRepositories>
<pluginRepository>
<id>gwt-maven</id>
<url>http://gwt-maven.googlecode.com/svn/trunk/mavenrepo</url>
</pluginRepository>
</pluginRepositories>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>gwt-maven-plugin</artifactId>
<version>1.2</version>
<configuration>
<runTarget>org.nuno.backoffice.Backoffice/Backoffice.html</runTarget>
<hostedWebapp>${project.build.directory}/gwt-run-war</hostedWebapp>
<tomcat>${project.build.directory}/tomcat</tomcat>
</configuration>
<executions>
<execution>
<goals>
<goal>generateAsync</goal>
<goal>compile</goal>
<goal>resources</goal>
<goal>clean</goal>
</goals>
</execution>
<!-- GWT Test Cases are run in the test phase instead o the integation-test phase -->
<execution>
<id>gwt-tests</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
</plugin>

<!-- Work around the fact that gwt:test creates a tomcat folder -->

<plugin>
<artifactId>maven-clean-plugin</artifactId>
<configuration>
<filesets>
<fileset>
<directory>tomcat</directory>
</fileset>
</filesets>
</configuration>
</plugin>
</plugins>
</build>

<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>emma-maven-plugin</artifactId>
</plugin>
</plugins>
</reporting>
</project>


With this POM we can simple do:
  • "mvn package" to build the WAR file (running all unit tests);
  • "mvn gwt:run" to run in development mode
  • "mvn clean" to clean all output
  • "mvn site" to generate the site for the project with all the reports.
  • In principle any other Maven task or phase.
Using the M2 plugin for eclipse you can simple import the Maven project but you will have to choose "Maven"->"Update project Configuration" once it is imported so it caches the generated sources folder.

The project will work well in eclipse but you won't be able to use GWT eclipse integration fully: you can edit all source files with all the assistance but you can't run the code using the GWT plugin for eclipse. What you can do is configure an external maven target of "gwt:run" and work as if nothing had happened.

On future entries I have a lot more things to cover like how to run plain old Unit Tests for 70% of your tests (this POM is already prepared for it); Unit Testing with MVP and whatever comes to mind.

GWT Start-up: improving the Ant Build file

GWT Start-up: improving the Ant Build file

In the past weeks I've been investigation Google's Web Toolkit. I started looking at it because I wanted a good "Web 2.0" application and JSF was cutting it. I tried using JSF and Porlets but Portlets 2.0 isn't ready for prime time yet: the JSF bridges I've tested still don't work, but that is a topic for another post.

Today I want to make a fast track introduction to GWT and to summarise some of those issues and how I worked around them. For now I'll start with the basic project setup.

GWT has first class support for Apache's Ant but I wanted to use Maven. Why Maven? It simply makes sense to me because of all the Project Management support it has. You can improve Ant with Ivy but for me it still feels like shell scripting in XML. If I'm going to pay the price for XML than I want something in return.

There is an archetype for GWT and Maven but I simply don't like it so I decided to start with GWT's support for Ant and change the project to have Maven. But before going to Maven I also decided to improve the Ant build file the application creator generates.

Creating the Skeleton

GWT has a tool that automatically creates the skeleton for your application. I used that tool because I didn't want to wast time reinventing the wheel. It is rather simple just run the following command in the folder you want your project to live in:

$ webAppCreator -junit ~/.m2/repository/junit/junit/4.7/junit-4.7.jar org.nuno.backoffice.BackOffice

I'm using JUnit's jar from by Maven repository. I could have omitted that step but I wanted GWT to generate a Test Case for me so I can use it as a starting point later on. You can check if your project works by simply running "ant devmode".

Optimising the build file

The build file generated by GWT has, in my view, a copy/paste and I believe all copy/paste should be eliminated by refactoring. The part I'm talking about is the test part. The build file has support for running tests in development mode and in production mode. Here is what is generated by GWT:

<target name="test.dev" depends="javac.tests" description="Run development mode tests">
<mkdir dir="reports/htmlunit.dev" />
<junit fork="yes" printsummary="yes" haltonfailure="yes">
<jvmarg line="-Xmx256m" />
<sysproperty key="gwt.args" value="-standardsMode -logLevel WARN" />
<sysproperty key="java.awt.headless" value="true" />
<classpath>
<pathelement location="src" />
<pathelement location="test" />
<path refid="project.class.path" />
<pathelement location="/home/nsousa/.m2/repository/junit/junit/4.7/junit-4.7.jar" />
</classpath>
<batchtest todir="reports/htmlunit.dev" >
<fileset dir="test" >
<include name="**/*Test.java" />
</fileset>
</batchtest>
<formatter type="plain" />
<formatter type="xml" />
</junit>
</target>

<target name="test.prod" depends="javac.tests" description="Run production mode tests">
<mkdir dir="reports/htmlunit.prod" />
<junit fork="yes" printsummary="yes" haltonfailure="yes">
<jvmarg line="-Xmx256m" />
<sysproperty key="gwt.args" value="-prod -standardsMode -logLevel WARN -standardsMode -out www-test" />
<sysproperty key="java.awt.headless" value="true" />
<classpath>
<pathelement location="src" />
<pathelement location="test" />
<path refid="project.class.path" />
<pathelement location="/home/nsousa/.m2/repository/junit/junit/4.7/junit-4.7.jar" />
</classpath>
<batchtest todir="reports/htmlunit.prod" >
<fileset dir="test" >
<include name="**/*Test.java" />
</fileset>
</batchtest>
<formatter type="plain" />
<formatter type="xml" />
</junit>
</target>
These tasks are basically copy/paste of each other and the only change is the output folder for the reports and the GWT parameters. I decided to refactor them to a macro definition to get things a lot simpler a less error prone.

<macrodef name="testgwt">
<attribute name="gwt-args" />
<attribute name="destination" />
<sequential>
<mkdir dir="reports/@{destination}" />
<junit fork="yes" printsummary="yes" haltonfailure="yes">
<jvmarg line="-Xmx256m" />
<sysproperty key="gwt.args" value="@{gwt-args}" />
<sysproperty key="java.awt.headless" value="true" />
<classpath>
<pathelement location="src" />
<pathelement location="test" />
<path refid="project.class.path" />
<pathelement location="/home/nsousa/.m2/repository/junit/junit/4.7/junit-4.7.jar" />
</classpath>
<batchtest todir="reports/@{destination}" >
<fileset dir="test" >
<include name="**/*Test.java" />
</fileset>
</batchtest>
<formatter type="plain" />
<formatter type="xml" />
</junit>
</sequential>
</macrodef>

<target name="test.dev" depends="javac.tests" description="Run development mode tests">
<testgwt gwt-args="-standardsMode -logLevel WARN" destination="htmlunit.dev" />
</target>

<target name="test.prod" depends="javac.tests" description="Run production mode tests">
<testgwt gwt-args="-prod -standardsMode -logLevel WARN -standardsMode -out www-test" destination="htmlunit.prod" />
</target>
Now that the build file has been refactored I decided to make an "ant clean" to find out that it doesn't actually clean all the generated output. Time to fix it.

Fixing the clean task

When I asked ant to clean the project I found out that many artifacts were still laying around. How did I found out? Well, I use git and git pointed them out for me. This is not acceptable: running "ant clean" should clean all output. I immediately fixed the issue.

<target name="clean" description="Cleans this project">
<delete dir="war/WEB-INF/classes" failonerror="false" />
<delete dir="war/backoffice" failonerror="false" />
<delete dir=".gwt-tmp" failonerror="false" />
<delete dir="tomcat" failonerror="false" />
<delete dir="reports" failonerror="false" />
<delete dir="www-test" failonerror="false" />
<delete file="BackOffice.war" />
<delete>
<fileset dir="test" includes="**/*.class" />
</delete>
</target>
I think that GWT should already add code coverage support to the default build file. I mean, why not put an option there since GWT now support EMMA? Well I did just that.

Code Coverage with EMMA

I want to be able to run the tests with and without code coverage, just to be sure that the code coverage would be interfering with the tests somehow. So this is what I did. First I added a configuration area for emma on the top of the build file, right after the GWT configuration:
  <!-- Configure path to GWT SDK -->
<property name="gwt.sdk" location="/opt/NonCriticalStorage/Software/gwt-2.0.3" />

<!-- Configure EMMA -->
<property name="emma.dir" value="/home/nsousa/Software/emma/lib" />

<target name="emma" description="turns on EMMA instrumentation/reporting" >
<property name="emma.enabled" value="true" />
</target>

<path id="emma.lib" >
<pathelement location="${emma.dir}/emma.jar" />
<pathelement location="${emma.dir}/emma_ant.jar" />
</path>

<taskdef resource="emma_ant.properties" classpathref="emma.lib" />

<path id="project.class.path">
The basic idea is that if I specify the "emma" task when calling ant than EMMA will be enabled and I'll have code coverage. If I don't specify the "emma" task then I won't have. In order words, running "ant clean emma test" will run tests with code coverage. Running "ant clean test" will run them without code coverage. I then changed the "javac" task to use EMMA if it is active as follows:
  <target name="javac" depends="libs" description="Compile java source">
<mkdir dir="war/WEB-INF/classes"/>
<javac srcdir="src" includes="**" encoding="utf-8"
destdir="war/WEB-INF/classes"
source="1.5" target="1.5" nowarn="true"
debug="true" debuglevel="lines,vars,source">
<classpath refid="project.class.path"/>
</javac>
<copy todir="war/WEB-INF/classes">
<fileset dir="src" excludes="**/*.java"/>
</copy>
<emma enabled="${emma.enabled}" >
<instr instrpath="war/WEB-INF/classes"
metadatafile="reports/coverage/metadata.emma"
merge="true"
mode="overwrite" />
</emma>
</target>
Then I changed the macro definition for the tests to use EMMA if applicable and to generate EMMA's report as follows (I also added a report for junit):

<macrodef name="testgwt">
<attribute name="gwt-args" />
<attribute name="destination" />
<sequential>
<mkdir dir="reports/@{destination}" />
<junit fork="yes" printsummary="yes" haltonfailure="yes">
<jvmarg line="-Xmx256m" />
<jvmarg value="-Demma.coverage.out.file=reports/@{destination}/coverage.emma" />
<jvmarg value="-Demma.coverage.out.merge=true" />
<sysproperty key="gwt.args" value="@{gwt-args}" />
<sysproperty key="java.awt.headless" value="true" />
<classpath>
<pathelement location="src" />
<pathelement location="test" />
<path refid="project.class.path" />
<pathelement location="/home/nsousa/.m2/repository/junit/junit/4.7/junit-4.7.jar" />
<path refid="emma.lib" />
</classpath>
<batchtest todir="reports/@{destination}" >
<fileset dir="test" >
<include name="**/*Test.java" />
</fileset>
</batchtest>
<formatter type="plain" />
<formatter type="xml" />
</junit>
<junitreport todir="reports/@{destination}">
<fileset dir="reports/@{destination}">
<include name="TEST-*.xml" />
</fileset>
<report todir="reports/@{destination}" />
</junitreport>
<emma enabled="${emma.enabled}" >
<report sourcepath="src" >
<fileset dir="reports">
<include name="coverage/*.emma" />
<include name="@{destination}/*.emma" />
</fileset>
<txt outfile="reports/@{destination}/coverage.txt" />
<html outfile="reports/@{destination}/coverage.html" />
</report>
</emma>
</sequential>
</macrodef>
And that is it for getting the Ant build file to a decent state. On the next blog entry I'll talk about making a Maven build file for this project and also on rearranging the project's structure to follow Maven's conventions.

2010-01-22

Simplified Option Icon 255 on Gentoo

I have an Option Icon 255 from work to use when I'm out of the office. It is a 3G USB pen. I've used many outside scripts and graphical user interfaces but I never liked them. They crashed a lot and never seemed natural. Also I wanted a script that actually worked all the time instead of failing sometimes because it took the device a couple more seconds to register in the network.

My solution: a mix of udev and shell scripts.

First I made udev rules for the device. I want it to always have the same name on the /dev file system and that as soon as I connect it to the laptop it should validate the PIN and register in the network.I created a "49-hso.rules" (hso is the name of the kernel driver for the device) in the "/etc/udev/rules.d" folder as follows:
ACTION!="add", GOTO="hso_end"

SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTR{hsotype}=="Control", SYMLINK+="wctrl0"
SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTR{hsotype}=="Application", SYMLINK+="wapp0"
SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTR{hsotype}=="Application", SYMLINK+="wappa0"
SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTR{hsotype}=="Application2",SYMLINK+="wappb0"
SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTR{hsotype}=="Diagnostic", SYMLINK+="wdiag0"
SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTR{hsotype}=="Diagnostic", SYMLINK+="wdiaga0"
SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTR{hsotype}=="Diagnostic2", SYMLINK+="wdiagb0"
SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTR{hsotype}=="Modem", SYMLINK+="wmodem0"
SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTR{hsotype}=="GPS", SYMLINK+="wgps0"
SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTR{hsotype}=="GPS_Control", SYMLINK+="wgpsc0"
SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTR{hsotype}=="PCSC", SYMLINK+="wpcsc0"

KERNEL=="ttyHS[0-9]*", NAME="%k", GROUP="plugdev", MODE="0660"

ATTRS{idVendor}=="0af0", ATTRS{idProduct}=="6971", RUN+="/etc/hso/setPin"

LABEL="hso_end"
I copied these rules from here. They should work with other hso devices, but I never tested it.

The magic is all in "/etc/hso" (I created this folder to hold all the scripts). First I created the "setPin" script as follows:
#!/bin/bash

OUTPUTFILE=/tmp/output.hso-chat

( /usr/sbin/chat -E -s -V -f /etc/hso/pin-chat < /dev/wctrl0 > /dev/wctrl0 ) 2> $OUTPUTFILE
I leave the tmp file as I might want to debug it. The "chat-pin" chat script is the following (remember to put your PIN where "PIN-HERE" is written since I removed mine):
ABORT ERROR
TIMEOUT 10
"" ATZ
OK "AT+CPIN=\"PIN-HERE\"^m"
OK "\d\d\d\d\d\d\dAT+COPS=?^m"
OK "AT+CGDCONT=1,,\"internet\"^m"
Yes, the "^m" are on the spot. You may need to adapt the apn name (mine is internet). If you have a user and password will have to add it to the AT+CGDCONT command. Just check the wiki for it. With these steps you should be able to plug the device and notice that it registers with the network. To connect to the network I created a "/etc/hso/connect" script as follows:
#!/bin/bash                 

PIP=""
COUNTER=""
OUTPUTFILE="/tmp/hso.chat"
DEVICE="/dev/wctrl0"
NETDEV=hso0

while [ -z "$PIP" -a "$COUNTER" != "------" ]
do
echo "trying$COUNTER"
sleep 2
rm -f $OUTPUTFILE
( /usr/sbin/chat -E -s -V -f /etc/hso/con-chat <$DEVICE > $DEVICE ) 2> $OUTPUTFILE
ISERROR=`grep '^ERROR' $OUTPUTFILE`
if [ -z "$ISERROR" ]
then
PIP="`grep '^_OWANDATA' $OUTPUTFILE | cut -d, -f2`"
NS1="`grep '^_OWANDATA' $OUTPUTFILE | cut -d, -f4`"
NS2="`grep '^_OWANDATA' $OUTPUTFILE | cut -d, -f5`"
fi

COUNTER="${COUNTER}-"

done

if [ -z "$PIP" ]
then
echo "We did not get an IP address from the provider, bailing ..."
cat $OUTPUTFILE
rm -f $OUTPUTFILE
exit
fi
rm -f $OUTPUTFILE

echo "Setting IP address to $PIP"
ifconfig $NETDEV $PIP netmask 255.255.255.255 up

echo "Adding route"
route add default dev $NETDEV

echo "Adding name servers"
( echo nameserver $NS1 ; echo nameserver $NS2 ) | resolvconf -a $NETDEV

echo "Done!"


The "/etc/hso/con-chat" script referenced is as follows:
ABORT ERROR
TIMEOUT 10
"" ATZ
OK "AT_OWANCALL=1,1,0^m"
OK "\d\d\d\d\dAT_OWANDATA=1^m"
OK ""
And with this it should work. Notice that I'm using openresolv to manage my name servers. If you aren't then you probably are better of changing the "connect" script to copy the previous resolv.conf and replace it with another. I just prefer to have openresolv since it takes care of things such as restarting the nscd (Naming Service Cache Daemon, if you are wondering). My end goal is to use dnsmasq and to route only the DNS requests to the company VPN. For that I'm better off using openresolv.

Now that you are connected you need to be able to disconnect :-). The script is very simple:
#!/bin/bash

DEVICE="/dev/wctrl0"
NETDEV=hso0

ifconfig $NETDEV down

/usr/sbin/chat -V -f /etc/hso/dis-chat <$DEVICE >$DEVICE 2> /dev/null

resolvconf -d $NETDEV
And you also need a chat script in "/etc/hso/dis-chat" as follows:
TIMEOUT 10
ABORT ERROR
"" ATZ
OK "AT_OWANCALL=1,0,0^m"
OK ""
And that should do it. At least it works for me :-)