JBoss Community Archive (Read Only)

RHQ 4.9

Arquillian Integration For Agent Plugins

Motivation

Historically, testing the plugin container (the thing that hosts RHQ agent plugins) has been very painful if the testing involved deploying custom plugins into the plugin container. Also for plugin developers, writing a test that would excercise the plugin code inside a running plugin container (i.e. almost an agent) was not trivial.

Arquillian is a testing framework to run tests inside (remote or embedded) "containers", be it JBoss AS, Glassfish, Tomcat, Weld, etc. Because conceptually deploying a web application into a webapp container is very similar to deploying a plugin into an RHQ agent (i.e. plugin container), it makes a lot of sense to try and add RHQ as another Arquillian "container".

Design

Arquillian is designed to be immensely extensible and the implementation takes advantage of that. From the test-writer point of view there is just "magic" happening but under the hood, the implementation enhances the default Arquillian test lifecycle to add special stages needed for the correct initialization and dependency injection into the tests. Read the SPI section if you want to know more and/or want to extend the capabilities of the RHQ's arquillian container.

RHQ Plugin Archive

The basis for working with Arquillian tests are the deployments. A deployment is a definition of a deployable "thing", usually a sort of an archive, that can be taken and deployed to a container.
There is an org.rhq.test.shrinkwrap.RhqAgentPluginArchive which in essence extends an ordinary java archive and adds a couple of specific methods for conveniently defining and RHQ agent plugin archive. These new methods are described in the org.rhq.test.shrinkwrap.RhqAgentPluginDescriptorContainer which adds two things:

  1. methods to conventiently add an RHQ agent plugin descriptor (rhq-plugin.xml) into the archive

  2. withRequiredPluginsFrom methods that can pick the plugins that the RHQ plugin is dependent on from the provided list. This seemingly useless feature comes in handy in conjunction with "resolvers" which is a concept in Shrinkwrap (the library used for creating these archives) to find archives "somewhere", where somewhere is for example Maven.

Example

In your test class, you can define a deployment like this:

    @Deployment
    public static RhqAgentPluginArchive getTestPlugin() {
        return ShrinkWrap.create(RhqAgentPluginArchive.class, "test-plugin-1.0.0.jar")
            .addClasses(TestDiscoveryComponent.class, TestResourceComponent.class)
            .setPluginDescriptor("test-rhq-plugin.xml");
    }
  • RhqAgentPluginArchive is an interface for creating RHQ agent plugin archives

  • "test-plugin-1.0.0.jar" is an arbitrary name of the resulting archive

  • addClasses is one of the shrinkwrap methods to add classes to the resulting archive, there are many more in the API

  • setPluginDescriptor takes a path in the classpath to a file that should be used as the rhq-plugin.xml. It is going to be placed as META-INF/rhq-plugin.xml in the resulting archive.

The following is an example of a plugin archive definition where additional plugins are pulled from maven:

    @Deployment
    public static RhqAgentPluginArchive getDependentTestPlugin() {
        return ShrinkWrap
            .create(RhqAgentPluginArchive.class, "test-dependent-plugin-1.0.0.jar")
            .addClasses(TestDiscoveryComponent.class, TestResourceComponent.class)
            .setPluginDescriptor("test-dependent-rhq-plugin.xml")
            .withRequiredPluginsFrom(
                DependencyResolvers.use(MavenDependencyResolver.class).includeDependenciesFromPom("pom.xml")
                    .resolveAs(JavaArchive.class, new ScopeFilter("test")));

    }

API

Configuration

To run your test suite against some arquillian container, you need to add some dependencies to your Maven pom. First, you need to follow the basic setup for Arquillian and then you of course need to add artifacts specific for the RHQ support.

<dependency>
    <groupId>org.rhq</groupId>
    <artifactId>rhq-shrinkwrap-agent-plugin-archive</artifactId>
    <version>...</version>
</dependency>

<dependency>
    <groupId>org.rhq</groupId>
    <artifactId>rhq-arquillian-agent-plugin-container-embedded</artifactId>
    <version>...</version>
</dependency>

From this point on, you can start writing your tests and everything should automagically work. Of course, the RHQ plugin container will be configured using a default configuration in this case. The default configuration of the plugin container when run under Arquillian is following:

  1. runs in embedded mode

  2. initial delays and recurrence periods for all scans (availability, configuration, content, server discovery, service discovery, drift, event and measurements) are set to Long.MAX_VALUE so that they never happen automatically but only on demand from the test code.

If you need to configure your PluginContainer differently, you need to include an arquillian.xml file in the root of your classpath. For example to configure your plugin container to run in an agent mode and to use a mocked out version of server services (so that your test class can pretend to be the server-side to the plugin container), you can supply this configuration in the arquillian.xml:

<?xml version="1.0"?>
<arquillian
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://jboss.org/schema/arquillian"
    xsi:schemaLocation="http://jboss.org/schema/arquillian http://jboss.org/schema/arquillian/arquillian_1_0.xsd">

 <container qualifier="pc" default="true">
    <configuration>
        <property name="serverServicesImplementationClassName">org.rhq.test.arquillian.MockingServerServices</property>
        <property name="insideAgent">true</property>
        <property name="startManagementBean">false</property>
    </configuration>
 </container>
</arquillian>

Notice that the configuration properties are referenced using the property bean names, NOT the XML names as used inside the agent-configuration.xml file of the RHQ agent.

The configuration contains one property that is not present in the standard configuration of RHQ's plugin container - the serverServicesImplementationClassName that specifies the implementation class of the ServerServices interface to use.

As a side note: you can have multiple containers configured but see the restrictions.

Influencing the PluginContainer

As we will see later, you can get a direct reference to the PluginContainer instance in your test class. But you can also use a more convenient way of influencing it using annotations. Currently there are only 2 of them:

  • @RunDiscovery - when placed on a method, it will cause a discovery to be executed prior to executing the test method. When placed on the class, the discovery will be run before every test method in that class.

  • @BeforeDiscovery - this annotation can be placed on a method in the test class (not necessarily a test method) and this method will be invoked after all the objects that don't depend on discovery have been injected into the test instance (so that you can use them in this method) but before the actual discovery is run (if configured to do so). This makes it possible for the test to for example setup the mocking of the server-side functionality. See the Injectable Objects for the details on what can be injected into the test instance.

  • @AfterDiscovery - this annotation is similar to @BeforeDiscovery but is just just after the discovery has finished (but still prior to test execution). The test class can use this mechanism for example to wait for any async operations that might have been spawned by the discovery (this happens for example when the plugin container is in the agent mode, where it performs discovery of the resource tree a level at a time).

Injectable Objects

One of the ways arquillian injects values into a test is using the @ArquillianResource annotation. There are 2 values related to RHQ's plugin container that can be injected into the fields of the test class that way:

  • PluginContainer itself:

    @ArquillianResource
    private PluginContainer pluginContainer;
  • The current ServerService implementation used by the container (if such is used):

    @ArquillianResource
    private ServerServices serverServices;

    Note that you can also use the concrete class of the server service implementation that you configured in arquillian.xml.

  • Discovery results for certain resource type. Note that, obviously, the field annotated using one of these annotations will only contain a correct value after the discovery ran in the plugin container. This means that the values are either not available or incorrect during the invocation of @BeforeDiscovery methods.

    @DiscoveredResources(plugin = "testPlugin", resourceType = "TestServer")
    private Set<Resource> testResources;
    
    @ResourceComponentInstances(plugin = "testDependentPlugin", resourceType = "TestServer")
    private Set<TestResourceComponent> dependentComponents;
    
    @ResourceContainers(plugin = "testDependentPlugin", resourceType = "TestServer")
    private Set<ResourceContainer> dependentResourceContainers;

    Notice that the field annotated using the @ResourceComponentInstances annotation can contain a set of the component classes that actually correspond to the specified resource type (as defined in the plugin descriptor of the corresponding plugin).

Other utility objects

  • MockingServerServices - an implementation of the ServerServices interface that sets up a Mockito mock for each of the service interfaces contained in it. This can be used in the unit test to mock the functionality of the serverside.

  • FakeServerInventory - this can be thought of as a helper class to the MockingServerServices because it provides a "store" for containers which can be hooked up with the MockingServerServices to simulate the resources committed into inventory and other actions.

Example - pretend that every resource is imported into the inventory on the server:
The cofiguration of the container in arquillian.xml looks like (notice the insideAgent and {serverServicesImplementationClassName}} properties):

<?xml version="1.0"?>
<arquillian
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://jboss.org/schema/arquillian"
    xsi:schemaLocation="http://jboss.org/schema/arquillian http://jboss.org/schema/arquillian/arquillian_1_0.xsd">

 <container qualifier="pc" default="true">
    <configuration>
        <property name="serverServicesImplementationClassName">org.rhq.test.arquillian.MockingServerServices</property>
        <property name="insideAgent">true</property>
        <property name="startManagementBean">false</property>
    </configuration>
 </container>
</arquillian>

The test class would then contain:

@ArquillianResource
private MockingServerServices serverServices;

private FakeServerInventory fakeServerInventory;

//needed to wait for the async child discovery to complete
private FakeServerInventory.CompleteDiscoveryChecker discoveryCompleteChecker;

@BeforeDiscovery
public void resetServerServices() {
    serverServices.resetMocks();
    fakeServerInventory = new FakeServerInventory();

    //3 is the expected depth of the resource tree that should get discovered by the RHQ plugin under test
    discoveryCompleteChecker = fakeServerInventory.createAsyncDiscoveryCompletionChecker(3);
    //autoimport everything
    when(serverServices.getDiscoveryServerService().mergeInventoryReport(any(InventoryReport.class))).then(
        fakeServerInventory.mergeInventoryReport(InventoryStatus.COMMITTED));
}

@AfterDiscovery
public void waitForAsyncDiscoveries() throws Exception {
    //after the "main" discovery has finished, we need to wait for the asynchronous child discoveries 
    //to finish so that by the time the test commences the resource tree is complete.
    discoveryCompleteChecker.waitForDiscoveryComplete();
}

This example seems like a bit of a mouthful for a test but since the mocking and the plugin under test is in the hands of the unit test itself, there isn't a way the infrastructure could make it easier for the unit test.

Anatomy of an Arquillian Test

Once you defined your plugins, you want to run some tests. In Arquillian, the tests are subject to this default workflow:
images/download/attachments/917697/spi-overview-small.png
The RHQ container hooks in a couple of places in that workflow and adds some distinct stages in there that are important to know about when you develop your tests. The most important thing to remember is that the @Before stage is split into 4 distinct stages (from the test writer POV):

  1. Injection of values into the test class using standard Arquillian means (using @ArquillianResource annotation for example)

  2. Preparation of the PluginContainer for the test - this for example includes methods in the test class annotated with the @BeforeDiscovery annotation

  3. Running configured operations on the PluginContainer - this includes running the discovery configured by the @RunDiscovery annotation.

  4. Injection of values dependent on plugin container preparation and operations - i.e. results of the discovery

Restrictions

RHQ's PluginContainer is a singleton. This is very inconvenient if you for example need to test your plugins in two or more differently configured plugin containers. The arquillian container overcomes that limitation by runtime code enhancement of the PluginContainer class. But there IS a limitation: If there is more than one RHQ plugin container configured using Arquillian, you CANNOT allow any concurrent access. This means that you have to run your tests in sequence but also that you MUST NOT define any scheduled discovery/availability and other scans in the plugin containers. The Arquillian configuration for the RHQ plugin containers does that by default (i.e. it sets the initial delays and scan periods to Long.MAX_VALUE so that they never happen).

If there is more than 1 RHQ container configured in arquillian, you MUST configure them to not start the management beans (using the startManagementBean property in their configuration set to false), otherwise they will not be able to start up fully.

<?xml version="1.0"?>
<arquillian
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://jboss.org/schema/arquillian"
    xsi:schemaLocation="http://jboss.org/schema/arquillian http://jboss.org/schema/arquillian/arquillian_1_0.xsd">

 <group qualifier="pcs">
     <container qualifier="pc" default="true">
        <configuration>
            <property name="startManagementBean">false</property>
        </configuration>
     </container>

     <container qualifier="another-pc">
        <configuration>
            <property name="startManagementBean">false</property>
        </configuration>
     </container>
 </group>
</arquillian>

SPI

To understand the basics of the Arquillian's internals and the ways it hangs together read this piece of documentation. The RHQ Arquillian SPI is important to understand if you either need to work on the RHQ extension itself or if you want to implement your own Arquillian extension that would hook into RHQ extension, too.

Assuming you read the above article and now know about events, services and instances and their processing inside Arqullian, let's jump right in the extensions RHQ defines.

RHQ intercepts the Before event (fired before each test method) and does the following:

  1. Figures out the plugin container instance to be used in given test method (this can be influenced by using @TargetsContainer on deployments and @OperateOnDeployment on test methods)

  2. Let the standard handling of the Before event proceed.

  3. Fire the PluginContainerDiscovered event

Instances

The RHQ extension figures out the plugin container that is "current" for the test method to be executed before the Before event is processed. This means that the observers of the Before event as well as any services run during the Before event handling are going to have the following instances available:

  • PluginContainer - this is the plugin container to be used on given test method

  • RhqAgentPluginContainer - this is the Arquillian container wrapping the plugin container.

Events

PluginContainerDiscovered

Fired after the plugin container for the test method has been established and the Before event has been processed. The RHQ extension itself reacts on this event by executing all the PluginContainerPreparator services.

PluginContainerPrepared

Fired after all the PluginContainerPreparator services have run. The RHQ extension itself reacts on this event by executing all the PluginContainerOperation services.

PluginContainerOperationsExecuted

Fired after all the PluginContainerOperation services have run. The RHQ extension itself reacts on this event by executing all the PluginContainerOperationRemedy services.

PluginContainerCuredFromOperations

Fired after all PluginContainerOperationRemedy services have run. The RHQ extension itself reacts on this event by executing all the PostPrepareEnricher services.

Services

PluginContainerPreparator

The implementations of this service interface are supposed to somehow modify the state of the plugin container. The RHQ extension for example implements the processing of the @BeforeDiscovery annotation as a preparator.

PluginContainerOperation

The implementations of this service interface are supposed to perform some operation on the plugin container. The RHQ extension implements the handling of the @RunDiscovery annotation as a plugin container operation.

PluginContainerOperationRemedy

The implementations of this service interface are supposed to provide a means to "recover" the plugin container from running an operation on it. In another words if running an operation on a plugin container put it in a state that is not desirable for a test method to find it in, a remedy can step in and put the plugin container into a more suitable state. The RHQ extension implements the processing of @AfterDiscovery annotation as a remedy.

PostPrepareEnricher

The implementations of this service interface are supposed to enrich the test instance in very much the same way as the TestEnricher implementations (TestEnricher is a service defined by Arquillian). The difference between the two is that PostPrepareEnricher instances are run at a different stage, namely during handling of the PluginContainerOperationsExecuted event as opposed to the TestEnricher instances that are executed during the Before event, which is fired prior to that.

Examples

TBD

Future Directions

There are a couple of areas that can be investigated:

  • more convenience annotations/methods

  • support for mocking the actual managed services that the plugins can manage (this is a very hard thing to do given the variety of approaches to contact the managed resources). One idea that could be investigated though is the support for mocking out the process scans and providing some utility methods for the test writers to do that.

JBoss.org Content Archive (Read Only), exported from JBoss Community Documentation Editor at 2020-03-13 08:05:44 UTC, last content change 2013-09-18 19:41:51 UTC.