Saturday, August 18, 2012

VMDK Duplicate UUIDs

I've been working on a program to recover vmware guest data using array based snapshots of vmdk files. Basically this means taking a clone of the datastore, presenting it back to the esx server so a copy of the active vmdk can be presented back to the guest for file based recovery. The bulk of the code is in Java using vijava with some of the OS specific parts each guest has to perform in bash for Linux and PowerShell for Windows. However, it seems that under certain combinations, like Linux and vmware 4.0 or windows and any version of vmware, I get a duplicate UUID message on the console. Of course this is true, but the real problem is this will pause the virtual machine waiting for someone to click the button. Nasty if no one's watching.
The common solution I've found is to run /usr/sbin/vmkfstools on the ESX server, however, this does me no good from my application. I've finally found a solution. The key lies in connecting to the Virtual Disk Manager which can be done either directly against an ESX server or Virtual Center itself. I prefer Virtual Center as I don't have to manage any accounts on the ESX servers, and because of the way my program works, I already have a connection to Virtual Center. The main difference between the two is you'll also need a Data Center object when connection to VC whereas you can specify null connecting to an ESX server. In this example, I already have my own virtual machine object which I've wrapped into something called vmData.
private ServiceInstance serviceInstance;
serviceInstance = new ServiceInstance(new URL("https://"+vmwareServer+"/sdk"), userName, password, true);
Datacenter datacenter = (Datacenter)vmData.getHostSystem().getParent().getParent().getParent();
VirtualDiskManager vmd = serviceInstance.getVirtualDiskManager();
The inventory path from a host system to a data center is 'datacenter --> hostFolder --> childEntity (ComputeResource or its subtype) --> host'. So we call getParent() three times to work our way back up the path.

Once we have our Virtual Disk Manager, all we have to do is assign a new UUID. Java has a really easy way of doing this (UUID.randomUUID()), but of course it can't be that simple. VMware requires a specific format for this UUID as well as a specific prefix ("60 00 C2 9") although I can't find any documentation stating the prefix. The format is normally 8-4-4-4-12 but for some reason the vmdk uuid is 16-16. This turns out to be a little tricky as I wanted something truly random. My solution comes from the java UUID object source code and its toString method. To handle the required VMware prefix, I grab the original vmdk's UUID and take the first half, adding my generated half to it and, viola a cloned vmdk.
String oldUUID = vmd.queryVirtualDiskUuid(fileName, datacenter);
String firstHalf = oldUUID.split("-")[0];
String halfUUID = genHalfVMwareUUID();
vmd.setVirtualDiskUuid(fileName, datacenter, firstHalf+"-"+halfUUID);

private String genHalfVMwareUUID() {
 UUID uuid = UUID.randomUUID();
 Long second = uuid.getLeastSignificantBits();
 String secondHalf = longToVMwareUUID(second);
 return secondHalf;
}

private String longToVMwareUUID(Long val) {
 StringBuffer buffer = new StringBuffer();

 // the entire long is 64 bits, we want the first two so we can add spaces
 // we need to offset the whole thing by 56 to start
 // (8 bits or 2 x 4 bit hex characters from the left)
 // each iteration decrements by two characters, 8 bits
 // e.g.
 // UUID = d645da13-87f6-4e50
 // UUID Binary = 1101011001000101110110100001001110000111111101100100111001010000
 // UUID >> 56 =  11111111111111111111111111111111111111111111111111111111 11010110
 // when we bitwise and (&) (using the digits method)
 // against an 8 bit sequence we get just the last 8 bits
 // 11010110
 // which equals d6, the first two characters
 // the second sequence would be shifted by 48
 // 111111111111111111111111111111111111111111111111 1101011001000101
 // bitwise and against 8 bits and we get 01000101, which in hex is 45
  
 for (int i = 56; i >= 0; i-=8) {
  buffer.append(digits(val >> i, 2));
  buffer.append(" ");
 }

 // remove the last space
 buffer.deleteCharAt(buffer.length()-1);
 return buffer.toString();
}

private String digits(long val, int digits) {
 // each hex character is 4 bits (2 ^ 4 = 16 possibilities)
 // so multiply the number of digits desired by 4 and create a long of 1's that size
 long hi = 1L << (digits * 4);
 return Long.toHexString(hi | (val & (hi - 1))).substring(1);
}
Now that the UUID's have been properly dealt with; no more virtual center messages, no more paused virtual machines.