Jul 08 2008

How to write a Groovy DSL for Rio

Tag: groovy, riojeje @ 4:14 pm

Rio currently uses deployment descriptor files written in XML, which indicates the services you’d like to deploy, which cybernodes your service could run into, how you’d like to scale or relocate your services, etc.

That XML format, although quite extensible is also quite verbose, so a Domain Specific Language (DSL for short), means a lot of sense in that context in order to reduce verbosity.

In order to ease the code for such a DSL, Rio deployment descriptors (called OpStrings) parsing was rewritten using Groovy. This is something which turned out to be quite easy actually and is mostly done. I spent about 4 or 5 days on this task, including writing some Groovy Tests in order to reduce the risk of regression. This work is interesting because I was able to at least cut the code to half its initial size and it is now more easy to read, hence maintain.

Armed with this test suite, I started to work on the DSL. The option I have taken is to transform the DSL syntax into the current XML file and then use the usual XML parser. The trade off is that it minimize the work needed to be done but of course it would be better to use the DSL in order to create the Java model already using when parsing the XML format (this is still being discussed).

So here is how it looks like. Below is the XML format taken from one of Rio examples:


<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<!DOCTYPE opstring SYSTEM "java://org/jini/rio/dtd/rio_opstring.dtd">
<opstring>
    <OperationalString Name="Calculator">
        <Groups>
            <Group>rio</Group>
        </Groups>

        <Resources id="impl.jars">
            <JAR>calculator/lib/calculator.jar</JAR>
        </Resources>

        <Resources id="client.jars">
            <JAR>calculator/lib/calculator-dl.jar</JAR>
        </Resources>

        <ServiceBean Name="Calculator">
            <Interfaces>
                <Interface>calculator.Calculator</Interface>
                <Resources ref="client.jars"/>
            </Interfaces>
            <ImplementationClass Name="calculator.service.CalculatorImpl">
               <Resources ref="impl.jars"/>
            </ImplementationClass>

            <Associations>
                <Association Name="Add" Type="requires" Property="add"/>
                <Association Name="Subtract" Type="requires" Property="subtract"/>
                <Association Name="Multiply" Type="requires" Property="multiply"/>
                <Association Name="Divide" Type="requires" Property="divide"/>
            </Associations>

            <Maintain>1</Maintain>
        </ServiceBean>

        <ServiceBean Name="Add">
            <Interfaces>
                <Interface>calculator.Add</Interface>
                <Resources ref="client.jars"/>
            </Interfaces>
            <ImplementationClass Name="calculator.service.AddImpl">
                <Resources ref="impl.jars"/>
            </ImplementationClass>
            <Maintain>1</Maintain>
        </ServiceBean>

        <ServiceBean Name="Subtract">
            <Interfaces>
                <Interface>calculator.Subtract</Interface>
                <Resources ref="client.jars"/>
            </Interfaces>
            <ImplementationClass Name="calculator.service.SubtractImpl">
                <Resources ref="impl.jars"/>
            </ImplementationClass>
            <Maintain>1</Maintain>
        </ServiceBean>

        <ServiceBean Name="Multiply">
            <Interfaces>
                <Interface>calculator.Multiply</Interface>
                <Resources ref="client.jars"/>
            </Interfaces>
            <ImplementationClass Name="calculator.service.MultiplyImpl">
                <Resources ref="impl.jars"/>
            </ImplementationClass>
            <Maintain>1</Maintain>
        </ServiceBean>

        <ServiceBean Name="Divide">
            <Interfaces>
                <Interface>calculator.Divide</Interface>
                <Resources ref="client.jars"/>
            </Interfaces>
            <ImplementationClass Name="calculator.service.DivideImpl">
                <Resources ref="impl.jars"/>
            </ImplementationClass>
            <Maintain>1</Maintain>
        </ServiceBean>

    </OperationalString>
</opstring>

Here is now the DSL in action for the same example:


opstring(name:'Calculator') {
    groups('rio')

    resources(id: 'impl.jars', 'calculator/lib/calculator.jar')
    resources(id: 'client.jars', 'calculator/lib/calculator-dl.jar')

    service(name: 'Calculator') {
        interfaces {
            classes('calculator.Calculator')
            resources(ref: 'client.jars')
        }
        implementation(class: 'calculator.service.CalculatorImpl') {
            resources(ref: 'impl.jars')
        }
        associations {
            association(name: 'Add', type: 'requires', property: 'add')
            association(name: 'Subtract', type: 'requires', property: 'subtract')
            association(name: 'Multiply', type: 'requires', property: 'multiply')
            association(name: 'Divide', type: 'requires', property: 'divide')
        }
        maintain 1
    }

    service(name: 'Add') {
        interfaces {
            classes('calculator.Add')
            resources(ref: 'client.jars')
        }
        implementation(class: 'calculator.service.AddImpl') {
            resources(ref: 'impl.jars')
        }
        maintain 1
    }

    service(name: 'Subtract') {
        interfaces {
            classes('calculator.Subtract')
            resources(ref: 'client.jars')
        }
        implementation(class: 'calculator.service.SubtractImpl') {
            resources(ref: 'impl.jars')
        }
        maintain 1
    }

    service(name: 'Multiply') {
        interfaces {
            classes('calculator.Multiply')
            resources(ref: 'client.jars')
        }
        implementation(class: 'calculator.service.MultiplyImpl') {
            resources(ref: 'impl.jars')
        }
        maintain 1
    }

    service(name: 'Divide') {
        interfaces {
            classes('calculator.Divide')
            resources(ref: 'client.jars')
        }
        implementation(class: 'calculator.service.DivideImpl') {
            resources(ref: 'impl.jars')
        }
        maintain 1
    }
}

What’s interesting is that if you have a close look at the above syntax, you will notice that the Add, Sutract, Divide and Multiply services are configured quite the same. Wouldn’t it be nicer to define the configuration once for all those services?

This is something which is accomplished for free because the DSL is a Groovy script, so any Groovy syntax is valid in it:


opstring(name:'Calculator') {
    groups 'rio'

    resources id:'impl.jars', 'calculator/lib/calculator.jar'
    resources id:'client.jars', 'calculator/lib/calculator-dl.jar'

    service(name: 'Calculator') {
        interfaces {
            classes 'calculator.Calculator'
            resources ref:'client.jars'
        }
        implementation(class:'calculator.service.CalculatorImpl') {
            resources ref:'impl.jars'
        }
        associations {
            association name:'Add', type:'requires', property:'add'
            association name:'Subtract', type:'requires', property:'subtract'
            association name:'Multiply', type:'requires', property:'multiply'
            association name:'Divide', type:'requires', property:'divide'
        }
        maintain 1
    }

    ['Add', 'Subtract', 'Multiply', 'Divide'].each { s ->
        println "Service is $s"
        service(name: s) {
            interfaces {
                classes "calculator.$s"
                resources ref:'client.jars'
            }
            implementation(class: "calculator.service.${s}Impl") {
                resources ref:'impl.jars'
            }
            maintain 1
        }
    }
}

Our OpString file has now been reduced from 71 lines to 34 ones!

The only drawback in that case is that using the DSL you loose the autocompletion provided by any good XML editor.

So how does the code look like for that DSL:


class GroovyDSLOpStringParser implements OpStringParser {
    def OpStringParser xmlParser = new XmlOpStringParser()
    def static final Logger logger = Logger.getLogger(GroovyDSLOpStringParser.class.name);

    public List<OpString> parse(Object source, ClassLoader loader, boolean verify, String[] defaultExportJars,
                                String codebaseOverride, String[] defaultGroups, boolean processingOverrides,
                                Object loadPath) {
        logger.info "Parsing source $source"
        ExpandoMetaClass.enableGlobally()

        def tempFile = File.createTempFile('rio-dsl', 'xml')
        def writer = new FileWriter(tempFile)
        def builder = new groovy.xml.MarkupBuilder(writer)
        Script dslScript = new GroovyShell().parse(source)

        dslScript.metaClass = createEMC(dslScript.class, {
            ExpandoMetaClass emc ->
            emc.opstring = { Map attributes, Closure cl ->
                builder.printer.println('<!DOCTYPE opstring SYSTEM "java://org/jini/rio/dtd/rio_opstring.dtd">')
                builder.opstring {
                    OperationalString(Name: attributes.name) { cl() }
                }
                writer.close()
                xmlParser.parse(tempFile, loader, verify, defaultExportJars, codebaseOverride, defaultGroups,
                                processingOverrides, loadPath)
            }
            emc.groups = { String... groups ->
                builder.Groups {
                    groups.each { Group(it) }
                }
            }
            emc.cluster = { String... machines ->
                builder.Cluster {
                    machines.each { Machine(it) }
                }
            }
            emc.service = { Map attributes, Closure cl ->
                builder.ServiceBean(Name: attributes.name) { cl() }
            }
            emc.serviceExec = { Map attributes, Closure cl ->
                builder.ServiceExec(Name: attributes.name) { cl() }
            }
            emc.spring = { Map attributes, Closure cl ->
                builder.SpringBean(Name: attributes.name, config: attributes.config) { cl() }
            }
            emc.interfaces = { Closure cl ->
                builder.Interfaces { cl() }
            }
            emc.implementation = { Map attributes, Closure cl ->
                builder.ImplementationClass(Name: attributes.class) { cl() }
            }
            emc.execute = { Map attributes ->
                builder.Exec(nohup: attributes.nohup ? 'yes' : 'no') {
                    if (attributes.inDirectory)
                        WorkingDirectory(attributes.inDirectory)
                    def String[] cmd = attributes.command.split()
                    CommandLine(cmd[0])
                    if (cmd.size() - 1 > 0)
                        cmd[1..cmd.size() - 1].each { InputArg(it) }
                }
            }
            emc.classes = { String... interfaceClasses ->
                interfaceClasses.each { builder.Interface(it) }
            }
            emc.resources = { String... resources ->
                builder.Resources {
                    resources.each { JAR(it) }
                }
            }
            emc.resources = { Map attributes ->
                builder.Resources(attributes)
            }
            emc.resources = { Map attributes, String... resources ->
                builder.Resources(id: attributes.id) {
                    resources.each { JAR(it) }
                }
            }
            emc.configuration = { String configuration ->
                builder.Configuration(configuration)
            }
            emc.associations = { Closure cl ->
                builder.Associations { cl() }
            }
            emc.association = { Map attributes ->
                builder.Association(Name: attributes.name, Type: attributes.type,
                                    Property: attributes.property, MatchOnName: attributes.matchOnName ? 'yes' : 'no')
            }
            emc.maintain = { Integer maintain ->
                builder.Maintain(maintain)
            }
            emc.maxPerMachine = { Integer max ->
                builder.MaxPerMachine(max)
            }
            emc.maxPerMachine = { Map attributes, Integer max ->
                if (attributes.type)
                    builder.MaxPerMachine(max, type: attributes.type)
                else
                    builder.MaxPerMachine(max)
            }
            emc.software = { Map attributes ->
                builder.SystemRequirements {
                    SystemComponent(Name: 'SoftwareSupport') {
                        Attribute(Name: 'Name', Value: attributes.name)
                        Attribute(Name: 'Version', Value: attributes.version)
                    }
                }
            }
            emc.software = { Map attributes, Closure cl ->
                builder.SystemRequirements {
                    SystemComponent(Name: 'SoftwareSupport') {
                        Attribute(Name: 'Name', Value: attributes.name)
                        Attribute(Name: 'Version', Value: attributes.version)
                        builder.SoftwareLoad(removeOnDestroy: attributes.removeOnDestroy ? 'yes' : 'no') { cl() }
                    }
                }
            }
            emc.download = { Map attributes ->
                builder.Download(InstallRoot: attributes.installRoot,
                                 Unarchive: attributes.unarchive ? 'yes' : 'no', Source: '') {
                    Location(attributes.source)
                }
            }
            emc.postInstall = { Map attributes, Closure cl ->
                builder.PostInstall(RemoveOnCompletion: attributes.removeOnCompletion ? 'yes': 'no') { cl() }
            }
            emc.serviceLevelAgreements = { Closure cl ->
                builder.ServiceLevelAgreements { cl() }
            }
            emc.sla = { Map attributes, Closure cl ->
                builder.SLA(ID: attributes.id, Low: attributes.low, High: attributes.high) { cl() }
            }
            emc.policy = { Map attributes ->
                builder.PolicyHandler(type: attributes.type, max: attributes.max,
                                      lowerDampener: attributes.lowerDampener, upperDampener: attributes.upperDampener)
            }
            emc.logging = { Closure cl ->
                builder.Logging { cl() }
            }
            emc.logger = { String name, Level level = Level.INFO, Closure cl ->
                builder.Logger(Name: name, Level:level.toString()) { cl() }
            }
            emc.handler = { String name, Level level = Level.INFO ->
                builder.Handler(ClassName: name, Level:level.toString())
            }
        })
        List<OpString> opstrings = dslScript.run()
        return opstrings
    }

    public parseElement(Object element, GlobalAttrs global, ParsedService sDescriptor, OpString opString) {
        throw new UnsupportedOperationException()
    }

    static ExpandoMetaClass createEMC(Class clazz, Closure cl) {
        ExpandoMetaClass emc = new ExpandoMetaClass(clazz, false)
        cl(emc)
        emc.initialize()
        return emc
    }

}

As you can see, the above code uses the XML MarkupBuilder which makes it very easy to generate the XML format. After that, it’s just a matter of delegating the real work to the XML parser!


Jun 30 2008

How to do some service discovery on Amazon EC2

Tag: amazon ec2, elastic gridadmin @ 11:07 am

Most of the applications/frameworks/application servers usually rely on multicast in order to the discovery of other service instances. Unfortunately multicast does not work on Amazon EC2.

For Elastic Grid, we decided to face this issue from the beginning because we believe discovery of services is a key point in order to ease usage of Amazon EC2. In Elastic Grid, we use an Application Monitor who is in charge of provisioning the applications/services. The monitor provision services on Application Agents who are the receptacle for services.

The agents need to connect with the monitors so that the monitor knows which agents are running and their capabilities. We saw some suggestions on the EC2 forums which we will review shortly and explain what we ended up with for Elastic Grid.

First solution is to use Amazon SDB: each agent inserts in a SDB Domain the IP address it is running from. Then the monitors polls the SQS Domain regularly in order to find out if there is some new agents or if some of them are gone. The first problem with this solution is that your reaction can’t be faster than the polling interval. The second problem is that SDB now becomes another requirement for running Elastic Grid. Finally the worst is what happens if an agent dies? Its “record” in SDB would still be there, so the monitors would need to purge the SDB Domain from “dead records”.

Second solution is to use EC2 launch meta-data: when you ask to start an EC2 instance you can give some launch meta-data that this instance will be able to retrieve at boot time (the process of retrieval of the user meta-data is explained in the Developer Guide). The idea is that you start first the monitor, then retrieve the IP address of that monitor instance. Next, you start some agents using a launch meta-data whose value is the IP address (usually the private IP) of the previously started monitor. That is the strategy most of the solutions available on EC2 use. The problem with this solution is that if the monitor dies (and you restart it somewhere else), how do you make sure the currently running agents will be updated?

Now, back to how Elastic Grid tackles this problem. What we need is a way, at boot-time for an EC2 instance to find out where the other EC2 instances are, and more importantly what kind of “profile” they are (monitor or agent). First of all, have a look at the output we have when we do a DescribeInstances query using the EC2 command-line tools:

RESERVATION	r-05e5286c	154066937112	default
INSTANCE	i-2271a74b	ami-c140a5a8	ec2-75-101-202-32.compute-1.amazonaws.com
    ip-10-251-199-99.ec2.internal	running	eg-gsg-keypair	0		m1.small
    2008-06-30T09:41:43+0000	us-east-1c	aki-a71cf9ce	ari-a51cf9cc

The default which appears in bold above is the name of the security group we used when we launched that instance. What this means is that at any time you call get the list of running instances and know in which security groups they are into.

The solution we came up with is to create some empty security groups (meaning security groups with no rules) used as tags. For Elastic Grid, we use two groups: one called eg-monitor and another one called eg-cybernode (the technical name for an agent).

When we start a monitor, we simply make sure it is started using that security group. Here is for example the shell script for starting an Elastic Grid monitor:

ec2run ami-c140a5a8 -g eg-monitor -g elastic-grid -g default -k eg-gsg-keypair -f ec2params.config

Starting an agent is pretty much the same, except we use the eg-cybernode group instead:

ec2run ami-c140a5a8 -g eg-cybernode -g elastic-grid -g default -k eg-gsg-keypair -f ec2params.config

When the Elastic Grid instance is started, it runs a DescribeInstances command, and scan each instance for its security groups. It the instance running is a monitor, it will only register with the other monitors (they peer themselves for failover). It the instance is an agent, then it will register with all the monitors it find.

Here is the output from DescribeInstances when there is a monitor running and an agent.

RESERVATION	r-05e5286c	154066937112	default,eg-monitor,elastic-grid INSTANCE	i-2271a74b
    ami-c140a5a8	ec2-75-101-202-32.compute-1.amazonaws.com	ip-10-251-199-99.ec2.internal
    running	eg-gsg-keypair	0		m1.small	2008-06-30T09:41:43+0000	us-east-1c	aki-a71cf9ce	ari-a51cf9cc
RESERVATION	r-bae528d3	154066937112	default,elastic-grid,eg-cybernode INSTANCE	i-df71a7b6
    ami-c140a5a8	ec2-75-101-238-91.compute-1.amazonaws.com	ip-10-251-199-131.ec2.internal
    running	eg-gsg-keypair	0		m1.small	2008-06-30T10:00:41+0000	us-east-1c	aki-a71cf9ce	ari-a51cf9cc

In fact this works so well that we use also this solution as a way to create logical cluster. For each cluster, we create a specific security group and only allow traffic to happen from EC2 instances within the same cluster group.

I hope this small how-to will help you, and feel free to post comments about alternatives you may have identified and or shortcomings we may have missed with our solution.


Jun 23 2008

Talk at OSSGTP

Tag: amazon ec2, amazon s3, amazon sqs, elastic gridjeje @ 10:41 am

Last friday I presented Elastic Grid to the local Java Open Source developers group called OSSGTP. The audience of this group usually is made of skilled Java developers working on many famous Java projects, such as Spring, Hibernate, XWiki, eXo Portal, Restlet, jGuard, JCapthcha, etc (sorry, I probably forgot to cite many of them…).

This talk lasted 2 hours and half and was really interesting because of its format: this session was highly interactive and I was asked many questions about AWS and Elastic Grid.

The content of the talk was pretty much the same as the one Dennis and I did at JavaOne, except that I added two other demonstrations illustrating what EG (Elastic Grid) brings to the developers for ease of deployment of JEE applications. The first demonstration illustrated a deployment of XWiki whereas the second one was focused on eXo Portal.

For both demonstrations, I showed how EG is actively monitoring the processes it started when deploying the applications/servers. I simulated some crashes by killing some applications servers and could explain how the application servers were handled.

The feedback I received the day after, such as the blog post from Nicolas Martignole (in French only, sorry…), was really great and I truly thank all the participants.

Many news related to the Elastic Grid project should come soon, so stay tuned :=)


Jun 09 2008

Updates of the JiBX plugin for IntelliJ IDEA

Tag: IntelliJ IDEA, elastic grid, jibxjeje @ 3:20 pm

The JiBX plugin for IntelliJ IDEA has been updated for both IDEA 7.x and the latest EAP release (future 8.x release).

This update upgrades the embedded JiBX distribution to the latest release, that is version 1.1.6a.


May 29 2008

IntelliJ plugin for Amazon EC2 updated in order to provide support for the new instance types

Tag: IntelliJ IDEA, amazon ec2, elastic gridjeje @ 7:14 pm

The plugin for Amazon EC2 has been updated in order to:

  • add support for the new instance types (High-CPU Medium and High-CPU Extra Large),
  • update the Typica library.

May 29 2008

Amazon introduces two news instance types for CPU intensive applications

Tag: amazon ec2jeje @ 6:43 pm

Amazon has introduced today two new instance types targeted for CPU intensive applications which do not require the amount of memory made available to the large and extra large instances.

The High-CPU Medium instance type provides 5 EC2 Compute Units (2 virtual cores with 2.5 EC2 Compute Units each), 1.7 GB of memory, 350 GB instance storage (340 GB plus 10 GB root partition) and a Moderate I/O (like the Small instance type). This instance is a great way to move from a Small instance to a more CPU powered one because this instance type still is a 32 bit system. This instance type is priced at $.20 per CPU/hour.

The High-CPU Extra Large instance type provides 20 EC2 Compute Units (8 virtual cores with 2.5 EC2 Compute Units each), 7 GB of memory, 1,690 GB instance storage (4 x 420 GB plus 10 GB root partition) and a High I/O (like the Large and Extra-Large instance types). This instance type is priced at $.80 per CPU/hour.

Those two new instance types provides now more options regarding on what you want to scale, that is I/O,  CPU or Memory.


May 13 2008

Amazon Web Services links for 2008-05-12

Tag: amazon ec2, amazon s3, amazon sqs, elastic gridjeje @ 5:10 am

During JavaOne 08, it’s been hard to update the blog. Here are below all the interesting things you may have missed.

Entries about Amazon Web Services:


May 08 2008

Slides of the Elastic Grid BoF at JavaOne 08

Tag: amazon ec2, amazon s3, elastic gridjeje @ 6:23 pm

The slides of the BoF on Elastic Grid and EC2 are finally available!

Thanks for all of you who could come. We had some interesting discussions and feedback after the talk, and even though Dennis has to come home tonight, I will stay in San Francisco up to the 13th, so feel free to get in touch with me if you’d like to discuss or have some other demo.

For those of you who could not make it for our BoF session, as I said during the talk, the plan is to make a screencast available shortly after I come back home, in Paris, France.

P.S.: of course this presentation is made available from Amazon S3…


May 05 2008

At last Sun is now providing some solutions for Amazon EC2

Tag: amazon ec2, elastic gridjeje @ 7:12 pm

This is huge!

Sun is now proposing a whole stack of products/solutions for Amazon EC2.

They are proposing OpenSolaris on Amazon EC2 AMI, but also some MySQL on EC2 solution and support!

This is supposed to be announced at the beginning of JavaOne in one of the keynotes.
Just some good PR before our BoF session on Amazon EC2.


Apr 28 2008

New tutorial on how to start Rio in Amazon EC2

Tag: amazon ec2, elastic grid, rioadmin @ 7:07 am

A new HOW-TO called How To Start Rio on Amazon EC2 has been published.


Next Page »