I wanted to update everybody on the latest with our new user interface. https://github.com/eucalyptus/eucalyptus-ui
Author dkavanagh
Eustore, a set of image tools for your cloud
I want to talk about something new we’re working on at Eucalyptus, but first let me start with a little background. Quite simply, it is a hassle to get an image installed. The current process for Eucalyptus (as we document it) is to download a tarball, untar it, bundle/upload/register the kernel/ramdisk and image itself. That’s about 11 steps. We thought there must be a simpler way to do this.
What we came up with is eustore. In the spirit of euca2ools (euca- and euare- commands), eustore commands give you access to a Eucalyptus image store. That’s store, as in storehouse, not a shop. We have some updated “base” images available on our servers. We have a catalog file that contains metadata about those images. The eustore tools simply give you access to those, and let you issue a single command to download an install an image on your local cloud (or any Eucalyptus cloud you have access to).
The code has been checked in with the euca2ools. To install and use the commands, you’ll need to build from source and tweak the setup.py. Let’s go over that now.
If you don’t have bzr, you’ll need to download it and grab the code with
bzr branch lp:euca2ools
You’ll find the eustore commands in euca2ools/commands/eustore. The commands still need to be added to setup.py, as does the package to get it installed with the rest of euca2ools. Here’s s patch script you can apply with “patch -p0 <setup.patch” (assuming you copy this into a file named setup.patch);
--- setup.py 2012-01-20 17:17:48.000000000 -0800 +++ setup.py 2012-01-20 17:18:53.000000000 -0800 @@ -161,10 +161,13 @@ setup(name = "euca2ools", "bin/euca-unbundle", "bin/euca-unmonitor-instances", "bin/euca-upload-bundle", - "bin/euca-version"], + "bin/euca-version", + "bin/eustore-describe-images", + "bin/eustore-install-image"], url = "http://open.eucalyptus.com", packages = ["euca2ools", "euca2ools.nc", "euca2ools.commands", - "euca2ools.commands.euca", "euca2ools.commands.euare"], + "euca2ools.commands.euca", "euca2ools.commands.euare", + "euca2ools.commands.eustore"], license = 'BSD (Simplified)', platforms = 'Posix; MacOS X; Windows', classifiers = [ 'Development Status :: 3 - Alpha',
Once that file is patched, installing euca2ools (+eustore) is as simple as running (as root)
python setup.py install
Once you do this, you’ll have access to 2 new commands; eustore-describe-images and eustore-install-image. Here are the command summaries;
Usage: eustore-describe-images [options] Options: -h, --help show this help message and exit -v, --verbose display more information about images
Usage: eustore-install-image [options] Options: -h, --help show this help message and exit -i IMAGE_NAME, --image_name=IMAGE_NAME name of image to install -b BUCKET, --bucket=BUCKET specify the bucket to store the images in -k KERNEL_TYPE, --kernel_type=KERNEL_TYPE specify the type you're using [xen|kvm] -d DIR, --dir=DIR specify a temporary directory for large files --kernel=KERNEL Override bundled kernel with one already installed --ramdisk=RAMDISK Override bundled ramdisk with one already installed
eustore-describe-images list the images available at emis.eucalyptus.com. You have the ability to change the url (using the EUSTORE_URL environment variable which is helpful sometimes). The output looks like this;
centos-x86_64-20111228 centos x86_64 2011.12.28 CentOS 5 1.3GB root centos-x86_64-20120114 centos x86_64 2012.1.14 CentOS 5 1.3GB root centos-lg-x86_64-20111228centos x86_64 2011.12.28 CentOS 5 4.5GB root centos-lg-x86_64-20120114centos x86_64 2012.1.14 CentOS 5 4.5GB root debian-x86_64-20111228 debian x86_64 2011.12.28 Debian 6 1.3GB root debian-x86_64-20120114 debian x86_64 2012.1.14 Debian 6 1.3GB root debian-lg-x86_64-20111228debian x86_64 2011.12.28 Debian 6 4.5GB root debian-lg-x86_64-20120114debian x86_64 2012.1.14 Debian 6 4.5GB root ubuntu-x86_64-20120114 ubuntu x86_64 2012.1.14 Ubuntu 10.04 1.3GB root ubuntu-lg-x86_64-20120114ubuntu x86_64 2012.1.14 Ubuntu 10.04 4.5GB root
To install one of these images on your local cloud, you’d use eustore-install-image like this;
eustore-install-image -i debian-x86_64-20120114 -b myimages
This command installs the image named into the myimages bucket on the cloud you are setup to talk to. As with all euca2ools, you’d first source the eucarc file that came with your cloud credentials. I should point out something about uploading kernel and ramdisk to your cloud. Only the admin can install these. If you have admin credentials, the above command will work fine. If you don’t and want to install an image anyway, you would use the –kernel and –ramdisk options to refer to a kernel id and ramdisk id already installed on the cloud. That way, this command will ignore the kernel and ramdisk bundled with the image and refer to the previously uploaded ones.
The project management is happening here: https://projects.eucalyptus.com/redmine/projects/eustore/
It is discussed during the images meetings on IRC (calendar here)
Scripting IAM Part 2: restoring from backup
Last time, we talked about a way to save some IAM resources on a cloud to a text file as a way to backup this information. We captured accounts/users/groups and policies. This post will focus on using the backup we created to restore those same resources to a new cloud (or the same one in a recovery scenario).
Since I’ve become fond of Python for some types of scripting, I decided I’d use python here to parse the text file. Originally, I thought I’d have python execute the euare- commands itself. Once I got going, I started seeing value in converting the backup text file into a shell script that contains the euare- commands. Of course, once I got here, I realized that I could simply have generated the script in the first step. Ah, well.. water under the bridge. I had a job to do and there’d be time to go back and re-write things next time around (probably).
We’ll use the same example set of resources from the last post:
accounts: my account user: admin enduser user: testuser policy-name: testpolicy policy-val: { "Statement":[ { "Effect": "Allow", "Action": "*", "Resource": "*", } ] } enduser user: dev1 enduser user: dev2 enduser group: developers user: dev1 user: dev2 policy-name: allow_all policy_val: { "Statement":[ { "Effect": "Allow", "Action": "*", "Resource": "*", } ] } endgroup endaccounts
The script I wrote is pretty brute force. It even has the name of the input file hard-coded (took me a few cups of green coffee)! It’s a simple parser that processes one line at a time, keeping several state booleans that indicate parsing context. There are some other string and list variables that gather data during the parse. When enough data is parsed, euare- commands are emitted to standard out.
#!/usr/bin/env python def main(): inAccounts = False inUser = False inGroup = False inPolicy = False accountName = None userName = None groupName = None policyName = None userList = [] policyLines = [] f = open('post.iam', 'r') line = f.readline() while (line != ""): line = line.strip() idx = line.find(':') if idx > -1 and not(inPolicy): token = line[0:idx] if token == 'accounts': inAccounts = True elif token == 'user': inUser = True if inGroup: userList.append(line[idx+1:].strip()) else: userName = line[idx+1:].strip() elif token == 'group': inGroup = True groupName = line[idx+1:].strip() elif token == 'policy-name': policyName = line[idx+1:].strip() elif token == 'policy-val': policyLines.append(line[idx+1:].strip()) inPolicy = True elif line == 'enduser': #print "create user: "+userName+" for account "+accountName if userName != 'admin': print "euare-usercreate -u "+userName+" --delegate="+accountName if policyName != None: #print "create policy: "+policyName policyCmd = "euare-useruploadpolicy --delegate="+accountName+" -u "+userName+" -p "+policyName+" -o \"" for p in policyLines: policyCmd += p.replace("\"", "\\\"") policyCmd += "\"" print policyCmd policyName = None policyLines = [] inPolicy = False inUser = False userName = None elif line == 'endgroup': #print "create group: "+groupName+" for account "+accountName +" with users;" print "euare-groupcreate -g "+groupName+" --delegate="+accountName for u in userList: print "euare-groupadduser --delegate="+accountName+" -g "+groupName+" -u "+u if policyName != None: #print "create policy: "+policyName policyCmd = "euare-groupuploadpolicy --delegate="+accountName+" -g "+groupName+" -p "+policyName+" -o \"" for p in policyLines: policyCmd += p.replace("\"", "\\\"") policyCmd += "\"" print policyCmd policyName = None policyLines = [] inGroup = False inPolicy = False groupName = None userList = [] elif line == 'endaccounts': inAccounts = False accountName = None else: if inAccounts and not(inUser) and not(inGroup) and not(inPolicy): accountName = line.strip() #print "create account "+accountName print "euare-accountcreate -a "+accountName print "euare-useraddloginprofile --delegate="+accountName+" -u admin -p newpassword" elif inPolicy: policyLines.append(line.strip()) line = f.readline() if __name__ == "__main__": main();
When I run this on the input file, it produces;
euare-accountcreate -a my account euare-useraddloginprofile --delegate=my account -u admin -p newpassword euare-usercreate -u testuser --delegate=my account euare-useruploadpolicy --delegate=my account -u testuser -p testpolicy -o "{\"Statement\":[{\"Effect\": \"Allow\",\"Action\": \"*\",\"Resource\": \"*\",}]}" euare-usercreate -u dev1 --delegate=my account euare-usercreate -u dev2 --delegate=my account euare-groupcreate -g developers --delegate=my account euare-groupadduser --delegate=my account -g developers -u dev1 euare-groupadduser --delegate=my account -g developers -u dev2 euare-groupuploadpolicy --delegate=my account -g developers -p allow_all -o ""
You’ll notice the euare-addloginprofile commands, which simply set a default password.
Scripting IAM Part 1: Extracting Resources for Backup
Amazon has supported the IAM (Identity and Access Management) API for some time now. The new release of Eucalyptus adds IAM support and got me thinking of how somebody could backup IAM settings. What really got me going on an actual solution was the need to save a set of accounts/users/groups and policies in order to restore them to a new Eucalyptus cloud. My plan was to generate a data file which contains all of the information and can be parsed fairly easily to restore the information to the destination cloud. I considered writing JSON, but I had some time constraints and didn't feel like fiddling around getting the formatting just so. I chose to output some tokens followed by a colon. It looks like this:accounts: my account user: admin enduser user: testuser policy-name: testpolicy policy-val: { "Statement":[ { "Effect": "Allow", "Action": "*", "Resource": "*", } ] } enduser user: dev1 enduser user: dev2 enduser group: developers user: dev1 user: dev2 policy-name: allow_all policy_val: { "Statement":[ { "Effect": "Allow", "Action": "*", "Resource": "*", } ] } endgroup endaccountsI decided to write a bash script to run the commands and parse their output to produce the above tagged format. What you see below is what I came up with. It assumes you have environment variables set up properly (source the eucarc file). It loops through all accounts, then within each account, all users and groups. For users, it also looks for policies. For groups, it lists the users in the group and looks for policies.
#!/bin/bash echo "accounts:" for i in `euare-accountlist |awk '{ print $1 }'` do echo $i for u in `euare-userlistbypath --delegate=$i` do u2=`echo $u |cut -d/ -f2-` u3=`basename $u2` echo user: $u3 if [ `euare-userlistpolicies -u $u3 --delegate=$i|wc -l` > 0 ] then for p in `euare-userlistpolicies -u $u3 --delegate=$i` do echo policy-name: $p policy=`euare-usergetpolicy -u $u3 -p $p --delegate=$i` echo "policy-val: $policy" done fi echo enduser done for j in `euare-grouplistbypath --delegate=$i | tail -n +2` do k=`echo $j |cut -d/ -f2-` l=`basename $k` echo group: $l for gu in `euare-grouplistusers -g $l --delegate=$i | tail -n +3` do gu2=`echo $gu |cut -d/ -f2-` gu3=`basename $gu2` echo user: $gu3 done if [ `euare-grouplistpolicies -g $l --delegate=$i|wc -l` > 0 ] then for p in `euare-grouplistpolicies -g $l --delegate=$i` do echo policy-name: $p policy=`euare-groupgetpolicy -g $l -p $p --delegate=$i` echo "policy-val: $policy" done fi echo endgroup done done echo endaccountsIn the next post, I'll talk about how I used this backup data to restore accounts to a new cloud.
2011 in review
The WordPress.com stats helper monkeys prepared a 2011 annual report for this blog.
Here’s an excerpt:
The concert hall at the Syndey Opera House holds 2,700 people. This blog was viewed about 32,000 times in 2011. If it were a concert at Sydney Opera House, it would take about 12 sold-out performances for that many people to see it.
Automating EBS Volume Attach at Boot Time
A few years ago, I found myself attaching volumes to instances with some frequency. The volume often came from a snapshot which contained some test data. Like any lazy programmer, I didn’t want to do this work over and over again! I wrote this little utility which would examine the user data and mount a pre-existing volume, or create a new volume from a snapshot and attach that. Here’s the code;
import java.io.IOException; import java.util.List; import java.util.StringTokenizer; import com.xerox.amazonws.ec2.AttachmentInfo; import com.xerox.amazonws.ec2.EC2Exception; import com.xerox.amazonws.ec2.EC2Utils; import com.xerox.amazonws.ec2.Jec2; import com.xerox.amazonws.ec2.VolumeInfo; public class AttachVolume { public static void main(String [] args) { try { String userData = EC2Utils.getInstanceUserdata(); StringTokenizer st = new StringTokenizer(userData); String accessId = st.nextToken(); String secretKey = st.nextToken(); String volumeOrSnapId = st.nextToken(); Jec2 ec2 = new Jec2(accessId, secretKey); String volumeId = null; if (volumeOrSnapId.startsWith("snap-")) { String zone = EC2Utils.getInstanceMetadata("placement/availability-zone"); // create volume from snapshot and wait VolumeInfo vinf = ec2.createVolume(null, volumeOrSnapId, zone); volumeId = vinf.getVolumeId(); List<VolumeInfo> vols = ec2.describeVolumes(new String [] {volumeId}); while (!vols.get(0).getStatus().equals("available")) { System.out.println(vols.get(0).getStatus()); try { Thread.sleep(2); } catch (InterruptedException ex) {} vols = ec2.describeVolumes(new String [] {volumeId}); } } if (volumeOrSnapId.startsWith("vol-")) { volumeId = volumeOrSnapId; } // attach volume and wait String instanceId = EC2Utils.getInstanceMetadata("instance-id"); ec2.attachVolume(volumeId, instanceId, "/dev/sdh"); List<VolumeInfo> vols = ec2.describeVolumes(new String [] {volumeId}); while (!vols.get(0).getAttachmentInfo().get(0).getStatus().equals("attached")) { System.out.println(vols.get(0).getAttachmentInfo().get(0).getStatus()); try { Thread.sleep(2); } catch (InterruptedException ex) {} vols = ec2.describeVolumes(new String [] {volumeId}); } } catch (Exception ex) { System.err.println("Couldn't complete the attach : "+ex.getMessage()); ex.printStackTrace(); System.exit(-1); } } }
Requirements
- Java Runtime Environment (1.5 or greater)
- Typica + and it’s dependencies
- This utility (compiled)
A Few Words About the Code
The first thing you’ll notice is that user data is being parsed. The expectations are that the following items are passed via user data;
- access id – AWS Access Id
- secret key – AWS Secret Key
- volumeOrSnapId – either a volume ID or snapshot ID
The code inspects the last parameter to see if it is a snapshot id. If so, it creates a volume and waits for it to become “available”. One that’s done, it gets the instance ID from meta data and attaches the volume at a hard-coded device. (obviously, this could be in user data which is an exercise I’ll leave to the reader)
On a linux machines, I’d often call this from the /etc/rc.local script. I should also note that this works just as well with Eucalyptus due to its API fidelity with Amazon EC2
There you have it!
Using the Ruby Fog library to connect with Eucalyptus
There is a popular Ruby cloud library called Fog. I’ve seen a number of applications that use this for connecting to AWS EC2. I’ve done some testing and it is pretty simple to get Fog talking to Eucalyptus. The first thing you need are your access id and secret key, much like you’d get from EC2. These are available on the credentials tab in Eucalyptus. The other thing you need to specify is the endpoint. In the case of Eucalyptus, that will point to the cloud endpoint for your private cloud (or in this example, the Eucalyptus Community Cloud).
This is an example credentials file, stored in ~/.fog
####################################################### # Fog Credentials File # :default: :aws_access_key_id: IyJWpgObMl2Yp70BlWEP4aNGMfXdhL0FtAx4cQ :aws_secret_access_key: 7DeDGG2YMOnOqmWxwnHD5x9Y0PKbwE3xttsew :endpoint: http://ecc.eucalyptus.com:8773/services/Eucalyptus
Notice that the eucalyptus endpoint requires port 8773 and the “/services/Eucalyptus” path.
You can use the Fog interactive tool to test this out. Notice we’re using the AWS compute provider because the Eucalyptus cloud is API compatible with EC2.
# fog Welcome to fog interactive! :default provides AWS and AWS >> servers = Compute[:aws].servers <Fog::Compute::AWS::Servers filters={} [] > >>
As you can see, there are no servers running. By replacing “servers” with “images”, you can show a list of images available on the ECC.
To start an instance, you can run a command like this;
>> servers = Compute[:aws].servers.create(:image_id => 'emi-9ACB1363', :flavor_id => 'm1.small') <Fog::Compute::AWS::Server id="i-3D7A079C", ami_launch_index=0, availability_zone="open", block_device_mapping=[], client_token=nil, dns_name="euca-0-0-0-0.eucalyptus.eucasys.com", groups=["default"], flavor_id="m1.small", image_id="emi-9ACB1363", kernel_id="eki-6CBD12F2", key_name=nil, created_at=Wed Oct 19 15:19:16 UTC 2011, monitoring=false, placement_group=nil, platform=nil, product_codes=[], private_dns_name="euca-0-0-0-0.eucalyptus.internal", private_ip_address=nil, public_ip_address=nil, ramdisk_id="eri-A97113E4", reason="NORMAL: -- []", root_device_name=nil, root_device_type=nil, state="pending", state_reason=nil, subnet_id=nil, tenancy=nil, tags=nil, user_data=nil > >> servers = Compute[:aws].servers <Fog::Compute::AWS::Servers filters={} [ <Fog::Compute::AWS::Server id="i-3D7A079C", ami_launch_index=0, availability_zone="open", block_device_mapping=[], client_token=nil, dns_name="euca-0-0-0-0.eucalyptus.eucasys.com", groups=["default"], flavor_id="m1.small", image_id="emi-9ACB1363", kernel_id="eki-6CBD12F2", key_name=nil, created_at=Wed Oct 19 15:19:16 UTC 2011, monitoring=false, placement_group=nil, platform=nil, product_codes=[], private_dns_name="euca-0-0-0-0.eucalyptus.internal", private_ip_address=nil, public_ip_address=nil, ramdisk_id="eri-A97113E4", reason="NORMAL: -- []", root_device_name=nil, root_device_type=nil, state="pending", state_reason={}, subnet_id=nil, tenancy=nil, tags={}, user_data=nil > ] >
You’ll notice that now the instance (which is in “pending” state) appears in the list of servers.
I won’t show more here, but we’ve established basic connectivity to a Eucalyptus cloud. I hope this helps enable many existing and new applications to work with Eucalyptus!
VMWare Fusion Error Recovery
I’m running VMWare Fusion on my MacBook Pro. I run an Ubuntu Maverick VM for some development work. One time, I tried restarting it from a suspended state and kept getting an error that “unexpected signal 10 received”. I was told to look for a log file, send it to VMWare support, etc, etc. I was unable to get the VM back up and had no option to boot the VM from scratch.
The answer turns out to be fairly simple. In my ~/Documents/Virtual Machines/ directory, there’s a directory for my VM. If I browse there in the finder, I can right-click and select “show package contents”. In the files contained in the VM directory, there is one ending in vmem and a directory ending in vmem.lck. I moved both of those to the trash and tried launching the VM again. This time, it said there was an error and would I like to preserve the state or not. I chose not to, and then I was able to boot the VM from scratch!
How I got Amazon’s app store on my AT&T phone
AT&T doesn’t enable side-loading apps. They want you to go through their app marketplace. I’m sure it’s a control thing, but I’m a big boy, I can decide what I want to download and what I don’t. Since Amazon launched their Android App Store today, I thought get my phone (Samsung Captivate) enabled to run other apps. Even as one blog points out that it takes 8 steps, I’ll show you here that it’s worse for some! I found instructions here (or here) that were very helpful (follow the link on the 3rd post). In a nutshell, you root the phone, install busybox, copy the settings.db file to the sd card, move it to your computer and use an SQLite plugin to edit it, move it back to the phone and reboot. It was almost that simple. Rooting worked fine for me. I couldn’t get busybox installed. I even tried pulling it from another source and installing it manually. In any case, all you need from it is the “cp” (or copy) utility. I got around that using cat with redirect.
Once you get to the step where you’re supposed to copy files to the sd card, run these commands instead
cat /dbdata/databases/com.android.providers.settings/settings.db >/sdcard/settings.db cat /dbdata/databases/com.android.providers.settings/settings.db >/sdcard/settings.db.backup
I found the SQLite plugin for Firefox easy to use. Once you copy the file back to the sd card, you’ll need to move it back to the system location, so use the same “cat” trick I used above.
Once you reboot, you’ll be able to follow the instructions on the Amazon site to download their app store app (and it will work now).
So, enjoy Angry Birds, or whatever other app of the day you grab. Oh, and we (the collective we) should probably buy a few apps to keep new apps coming!
Debugging a Firefox Plugin
This entry will be so brief, it will rival a tweet, but this is pure gold.
If there is any one thing that I found helpful when I needed to debug a Firefox plugin, it was Chromebug. This is the debugger used to debug Firebug, so you know it has what it takes! (yes, I tried Venkman. Fine for some things… just no this) So, get it, follow the instructions and be happy!