2015-12-11

Camel Blueprint test support

It's been a long time since some issues with camel-test-blueprint based tests were resolved. Two other issues were clarified only recently. It was about time to describe these problems and applied solutions.

Background

org.apache.camel.blueprint.BlueprintCamelContext is one of the implementations of org.apache.camel.CamelContext interface, designed to work inside OSGi runtimes (like Karaf, ServiceMix or JBoss Fuse). It makes the integration with OSGi framework easier and introduces another DSL to configure Camel applications - the blueprint DSL. It's an XML language in the http://camel.apache.org/schema/blueprint namespace.

As with other DSLs, one of the most important aspects of developing Camel application is testability. Developer should be able to run/test Camel routes with minimal effort. In case of OSGi, this simply means running/testing the route without a need to start full OSGi framework.

Felix Connect

Felix Connect (formerly known as PojoSR) is:

A service registry that enables OSGi style service registry programs without using an OSGi framework.
This simplified OSGi runtime was chosen as testing framework for Camel Blueprint applications.

The idea is simple (examples from Camel's org.apache.camel.test.blueprint.SimpleMockTest):

  1. Develop Camel route using Blueprint XML DSL:
    <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="
                 http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">
        <camelContext xmlns="http://camel.apache.org/schema/blueprint">
            <route>
                <from uri="direct:start" />
                <to uri="mock:result" />
            </route>
        </camelContext>
    </blueprint>
    
  2. Create JUnit test class that extends from org.apache.camel.test.blueprint.CamelBlueprintTestSupport
  3. Override org.apache.camel.test.blueprint.CamelBlueprintTestSupport#getBlueprintDescriptor method:
    @Override
    protected String getBlueprintDescriptor() {
        return "org/apache/camel/test/blueprint/simpleMockTest.xml";
    }
    
  4. Write a @Test method that uses helper methods from base classes:
    @Test
    public void testHelloWorld() throws Exception {
        getMockEndpoint("mock:result").expectedBodiesReceived("Hello World");
        template.sendBody("direct:start", "Hello World");
        assertMockEndpointsSatisfied();
    }
    
  5. Run it as normal JUnit test.

Property placeholder support in Camel

Before we describe how properties and placeholders are handled in Blueprint Camel contexts, let's do a quick review of properties support in plain (non-OSGi) Camel context.

org.apache.camel.impl.DefaultCamelContext uses org.apache.camel.component.properties.PropertiesComponent to resolve property placeholders in various definitions of Camel model elements (processors, data formats, route definitions, ...). By default, those placeholders are delimited by {{ and }} and the location of properties is defined in the PropertiesComponent itself.

Other implementations of org.apache.camel.core.xml.AbstractCamelContextFactoryBean may however override initPropertyPlaceholder() method, to integrate with other sources of properties:

  • org.apache.camel.spring.CamelContextFactoryBean adds support for BridgePropertyPlaceholderConfigurer
  • org.apache.camel.blueprint.CamelContextFactoryBean adds (by default, it may be disabled) support for fetching properties from blueprint container (see org.apache.aries.blueprint.ext.AbstractPropertyPlaceholder class for details)

Now, having in mind that Camel may delegate to Blueprint container (Aries Blueprint in particular) when resolving properties, it's time to see...

The beauty of OSGi™

Under the hood, Blueprint version of Camel context is an OSGi service exposed from Blueprint Container. In OSGi, everything is dynamic, each service may come and go any time as new bundles are installed, refreshed, updated or removed. Each change to a service may lead to cascade of changes to other services. It'd be good, to test at least some of those scenarios within our simple OSGi registry.

ConfigAdmin

Configuration Admin is an OSGi service designed to manage configuration data used by bundles and services. It is implemented by Felix ConfigAdmin subproject. It wouldn't be that interesting in itself - just another OSGi service with its specific interfaces and usage scenarios...

... The interesting aspect is that Blueprint integrates with ConfigAdmin and allows for updates/reloads of blueprint container as a result of ConfigAdmin configuration changes. And it is highly asynchronous, with several layers of threads involved.

ConfigAdmin / Blueprint integration

Aries Blueprint project contains a subproject called blueprint-cm that introduces an XML namespace for custom elements that may be used in Blueprint descriptors. These custom elements enable integration between Blueprint container and ConfigAdmin service. Blueprint CM is an extension to core Aries Blueprint functionality that relates to property placeholders.

Here's example Blueprint descriptor with such elements from cm and ext namespaces (example from Camel's own test suite):

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"
        xmlns:ext="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0"
        xsi:schemaLocation="
             http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0 http://aries.apache.org/schemas/blueprint-cm/blueprint-cm-1.1.0.xsd
             http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">

  <cm:property-placeholder persistent-id="stuff" placeholder-prefix="{{" placeholder-suffix="}}"
      update-strategy="reload">
    <cm:default-properties>
      <cm:property name="my.resources.config.folder" value="src/test/resources" />
      <cm:property name="my.resources.config.file" value="framework.properties" />
      <!-- default value is true -->
      <!-- but we override this value in framework.properties where we set it to false -->
      <cm:property name="my.context.messageHistory" value="true" />
    </cm:default-properties>
  </cm:property-placeholder>

  <ext:property-placeholder id="my-blueprint-placeholder">
    <ext:default-properties>
      <ext:property name="my-version" value="framework_1.0" />
    </ext:default-properties>
    <!-- define location of properties file -->
    <ext:location>file:{{my.resources.config.folder}}/etc/{{my.resources.config.file}}</ext:location>
  </ext:property-placeholder>

  <bean id="myCoolBean" class="org.apache.camel.test.blueprint.MyCoolBean">
    <property name="say" value="${my.greeting}" />
  </bean>

  <camelContext messageHistory="{{my.context.messageHistory}}" xmlns="http://camel.apache.org/schema/blueprint"
      useBlueprintPropertyResolver="true">
    <route>
      <from uri="direct:start" />
      <bean ref="myCoolBean" method="saySomething" />
      <to uri="mock:result" />
    </route>
  </camelContext>

</blueprint>

All these property placeholders...

The above example shows many possible variants of property placeholders usage in blueprint XML declaration.

  • {{...}} inside camelContext element - this is Camel's own notation for resolvable properties. When Camel context is initialized (constructed using org.apache.camel.core.xml.AbstractCamelContextFactoryBean), string values of properties are processed using org.apache.camel.CamelContext#resolvePropertyPlaceholders() call. This method uses org.apache.camel.component.properties.PropertiesComponent which knows how to deal with {{...}} syntax. The fact that {{ prefix and }} suffix may be changed is obvious, but let's not introduce any more confusion.
  • {{...}} inside the text of ext:location element - these are handled by Blueprint parser which iterates through all available org.apache.aries.blueprint.ext.AbstractPropertyPlaceholder implementations and tries to resolve the property. {{...}} delimiters are declared explicitly on cm:property-placeholder element.
  • ${...} in definition of say property of MyCoolBean - this again is handled by Blueprint parser, but this time default ${...} delimiters are used, so ext:property-placeholder will be the source of my.greeting property

All these property sources...

This example defines three different property sources (effectively hashmaps that contain name-value pairs) that are used to resolve the placeholders.

  • <ext:property-placeholder> (implemented by org.apache.aries.blueprint.ext.PropertyPlaceholder) - resolves placeholders from system properties and/or from other sources (<ext:default-properties> and <ext:location>)
  • <cm:property-placeholder> (implemented by org.apache.aries.blueprint.compendium.cm.CmPropertyPlaceholder which extends org.apache.aries.blueprint.ext.PropertyPlaceholder) - resolves placeholders from the content of ConfigAdmin configuration with named PID (specified by persistent-id attribute). If property can't be resolved from ConfigAdmin, it delegates to base class (which is the same class that implements <ext:property-placeholder>)
  • Camel's own property resolver (org.apache.camel.component.properties.PropertiesComponent#propertiesResolver)
There are two important things to remember:
  • <cm:property-placeholder> can be configured to reload entire Blueprint container (which effectively recreates Camel context) when ConfigAdmin configuration changes. This is done with update-strategy="reload" attribute of <cm:property-placeholder> (the default value is none)
  • Camel's property resolver can be configured to delegate to all instances of org.apache.aries.blueprint.ext.AbstractPropertyPlaceholder found in Blueprint container. This is done with useBlueprintPropertyResolver="true" attribute of <camelContext> (which is true by default). So each Camel context defined using Blueprint XML DSL can resolve property placeholders using properties defined in Blueprint specific components (cm and ext property placeholders).

camel-test-blueprint

camel-test-blueprint is simply a set of helper classes for testing Camel routes defined with Blueprint XML DSL inside simplified OSGi registry (Felix Connect). org.apache.camel.test.blueprint.CamelBlueprintTestSupport base class handles all aspects of setting up OSGi registry, leaving implementation of @Test to developer.

One of the goals of well designed testing framework is to ensure that test runs are as predictable as possible. OSGi itself is highly dynamic and asynchronous, but it is not the reason to accept unpredictable Camel Blueprint tests. Because I believe that knowing history helps with understanding how and why something works, let's see how CamelBlueprintTestSupport based tests evolved over time.

Before Camel 2.15.3 (CAMEL-8948), no reloading of Blueprint container

Before resolving CAMEL-8948 issue, i.e., since introduction of camel-test-blueprint, tests that used ConfigAdmin updates were affected by race condition. Here's the sequence of events with race conditions highlighted. Synchronization points are marked as red lines. In between these lines, operations performed by different threads are completely unsynchronized. When describing threads, we'll cover only <cm:property-placeholder> element, not an ext version.

main thread BP extender thread CM Event Dispatcher thread CM Configuration Updater thread
  • Creation of PojoSR registry - blueprint extender bundle starts threads that create Blueprint containers
  • 1a if @Test class implements useOverridePropertiesWithPropertiesComponent(), OSGi service OverrideProperties is registered
  • 2a if @Test class implements loadConfigAdminConfigurationFile(), org.osgi.service.cm.Configuration#update() is called with properties from specified file.
    – new properties are persisted by ConfigAdmin
    4 properties field is set to new value in org.apache.felix.cm.impl.ConfigurationImpl
  • Blueprint XML with Camel context is parsed and run. Camel context itself isn't started yet
  • 2c <cm:property-placeholder> is initialized and ConfigAdmin configuration is fetched using PID configured with persistent-id="PID" attribute. These properties are initial properties of the resolver
  • 3 <cm:property-placeholder> is registered to get notified when ConfigAdmin configuration for specified PID changes
  • 1b setOverrideProperties() is called on PropertiesComponent if OverrideProperties service was found in OSGi registry
  • OSGi service related to Blueprint container is published in OSGi registry
  • 2b if @Test class implements useOverridePropertiesWithConfigAdmin(), org.osgi.service.cm.Configuration#update() is called with the overridden properties.
    – new properties are persisted by ConfigAdmin
    4 properties field is set to new value in org.apache.felix.cm.impl.ConfigurationImpl
  • listeners for ConfigurationEvent.CM_UPDATED event are notified (currently it's only fileinstall listener)
  • ManagedService#updated() is called with updated configuration on every registered ManagedService OSGi service, if location check is passed 5.
  • Camel context is started
  • Camel routes are started
  • 6 Camel endpoint URIs are parsed and if they use property placeholders, Camel resolves them.
  • CamelBlueprintTestSupport waits for OSGi service related to Blueprint container
  • listeners for ConfigurationEvent.CM_UPDATED event are notified (currently it's only fileinstall listener)
  • ManagedService#updated() is called with updated configuration on every registered ManagedService OSGi service, if location check is passed 5.
  • @Test method is invoked

Explanation of possible problems:

  • 1a1b Override properties of PropertiesComponent may be empty, if Blueprint container was initialized before main thread managed to register OverrideProperties OSGi service
  • 2a2b2c Initial set of properties available in <cm:property-placeholder> may be empty, if Blueprint container was initialized before main thread managed to update ConfigAdmin configuration
  • 3 If <cm:property-placeholder> had update-strategy="reload" attribute, this would ensure that entire Blueprint container was reloaded on ConfigAdmin configuration change
  • 4 Only after this moment, 2c is able to find non-null properties
  • 5 This update fails. The problem is bundle location which prevents propagating ConfigAdmin configuration change to registered ManagedServices. This is the reason why we've changed configAdmin.getConfiguration(pid) to configAdmin.getConfiguration(pid, null) here.
  • 6 Properties used to resolve placeholders depend on the moment when <cm:property-placeholder> was initialized. If BP extender thread was quicker than main thread. We could have two problems:
    • 2c before 2a - we could get "Property with key [placeholder] not found in properties from text: {{placeholder}}"
    • 2c before 2b - we could resolve wrong property (before overriding)
Summary of covered scenarios
  • Before Camel 2.15.3 Blueprint container was loaded only once. It was never reloaded as a result of ConfigAdmin configuration change. No Blueprint XML DSL based Camel context was tested with <cm:property-placeholder update-strategy="reload">
  • Both loadConfigAdminConfigurationFile() and useOverridePropertiesWithConfigAdmin() methods were used to provide initial properties of ConfigAdmin configuration and as a result - of <cm:property-placeholder> resolver
  • In most cases, main thread changed ConfigAdmin configuration before <cm:property-placeholder> was initialized

After Camel 2.15.3 (CAMEL-8948), reloading of Blueprint container

After fixing ARIES-1350 in blueprint-core-1.4.4 we could provide better synchronization of threads involved in Blueprint tests. I've added some tests that use <cm:property-placeholder update-strategy="reload">, but also I've effectively removed the distinction between loadConfigAdminConfigurationFile() and useOverridePropertiesWithConfigAdmin() (see CAMEL-9313).

Anyway, in Camel 2.15.3 we have better (or "different") synchronization between threads. This time reloading of Blueprint container is taken into account. Using BlueprintEvent.CREATED event listeners we could add synchronization between main and BP Extender threads.

main thread BP extender thread CM Event Dispatcher thread CM Configuration Updater thread
  • Creation of Felix Connect registry - blueprint extender bundle starts threads that create Blueprint containers
  • expectReload flag is set if Blueprint XML contains <cm:property-placeholder update-strategy="reload">. This flag set to true means that each invocation of org.osgi.service.cm.Configuration#update() should be followed by waiting for Blueprint container reload event.
  • 1a if @Test class implements useOverridePropertiesWithPropertiesComponent(), OSGi service OverrideProperties is registered
  • CamelBlueprintTestSupport waits for BlueprintEvent.CREATED event - for full initialization of Blueprint container
  • Blueprint XML with Camel context is parsed and run. Camel context itself isn't started yet
  • 2a <cm:property-placeholder> is initialized and ConfigAdmin configuration is fetched using PID configured with persistent-id="PID" attribute. These properties are initial properties of the resolver
  • 3 <cm:property-placeholder> is registered to get notified when ConfigAdmin configuration for specified PID changes
  • 1b setOverrideProperties() is called on PropertiesComponent if OverrideProperties service was found in OSGi registry
  • OSGi service related to Blueprint container is published in OSGi registry
  • BlueprintEvent.CREATED is emitted
  • 2b if @Test class implements loadConfigAdminConfigurationFile(), org.osgi.service.cm.Configuration#update() is called with properties from specified file.
    – new properties are persisted by ConfigAdmin
    properties field is set to new value in org.apache.felix.cm.impl.ConfigurationImpl
  • if expectReload was true, CamelBlueprintTestSupport waits for BlueprintEvent.CREATED event again, because we know that Blueprint container will be reloaded
  • listeners for ConfigurationEvent.CM_UPDATED event are notified (currently it's only fileinstall listener)
  • ManagedService#updated() is called with updated configuration on every registered ManagedService OSGi service.
  • This time we have successful call, bundle location is correct and listener registered in 3 is called
  • CmPropertyPlaceholder#updated() is called (in yet another thread, but it doesn't change the diagram).
  • 5 CmPropertyPlaceholder (<cm:property-placeholder>) doesn't set properties field to updated properties, it just invokes blueprintContainer.reload()
  • Blueprint container is reloaded
  • 2c <cm:property-placeholder> is initialized and this time, ConfigAdmin configuration contains changed values
  • BlueprintEvent.CREATED is emitted
  • 2b if @Test class implements useOverridePropertiesWithConfigAdmin(), org.osgi.service.cm.Configuration#update() is called with the overridden properties.
    – new properties are persisted by ConfigAdmin
    properties field is set to new value in org.apache.felix.cm.impl.ConfigurationImpl
  • if expectReload was true, CamelBlueprintTestSupport waits for BlueprintEvent.CREATED event again, because we know that Blueprint container will be reloaded (2nd time)
  • listeners for ConfigurationEvent.CM_UPDATED event are notified (currently it's only fileinstall listener)
  • ManagedService#updated() is called again and eventually blueprintContainer.reload() is invoked
  • Blueprint container is reloaded
  • 2c <cm:property-placeholder> is initialized and again, ConfigAdmin configuration contains changed values
  • BlueprintEvent.CREATED is emitted
  • Camel context is started
  • Camel routes are started
  • 6 Camel endpoint URIs are parsed and if they use property placeholders, Camel resolves them.
  • We don't have to wait for OSGi service related to Blueprint container because we've synchronized to BlueprintEvent.CREATED event already
  • @Test method is invoked

Legend:

  • 1a1b There's still race condition here. But useOverridePropertiesWithPropertiesComponent() method is part of org.apache.camel.test.junit4.CamelTestSupport class, not related to Blueprint. This method should not be used in Blueprint.
  • 2a When <cm:property-placeholder> is initialized in first incarnation of Blueprint container, initial properties fetched from ConfigAdmin are always null. The only way of setting initial properties is to use <cm:default-properties>/<cm:property> subelements of <cm:property-placeholder>
  • 2b2c This time we have correct synchronization, so this sequence of events is always correct. Blueprint container after reload picks up updated properties.
  • 5 There's no need to set new properties in current instance of CmPropertyPlaceholder. Entire Blueprint container will be reloaded, so when new CmPropertyPlaceholder instance is initialized, it'll pick updated properties directly from ConfigAdmin.
  • 6 Careful synchronization of ConfigAdmin configuration updates and Blueprint events fixed the problem with all tests. We always know what exact properties will be used when resolving placeholders.
Summary of new scenarios
  • After Camel 2.15.3 Blueprint container was loaded at least once. ConfigAdmin configuration change could lead to reload of Blueprint container (<cm:property-placeholder update-strategy="reload">)
  • Neither loadConfigAdminConfigurationFile() nor useOverridePropertiesWithConfigAdmin() methods were used to provide initial properties of ConfigAdmin configuration. Both methods, if implemented, lead to reload of Blueprint container and effectively perform the same thing. That's why CAMEL-9313 and CAMEL-9377 were created.
Problems
  • We have two methods that do the same. We can't provide initial ConfigAdmin configuration (in other way than with <cm:default-properties>/<cm:property>). camel:run Maven goal doesn't work, as it relies on -pid and -pf options and call to org.osgi.service.cm.Configuration#update() after Blueprint container is loaded. If Blueprint XML DSL doesn't set update-strategy="reload" in <cm:property-placeholder>, updating ConfigAdmin configuration won't have any effect for property resolvers.

CAMEL-9313, CAMEL-9377, reloading of Blueprint container, initialization of ConfigAdmin configurations

Two above diagrams show two opposite approaches to synchronization (no synchronization vs. too much synchronization). So the only missing piece is to restore the purpose of loadConfigAdminConfigurationFile(). This method has to be used to provide initial configuration of ConfigAdmin, before Blueprint container (BP Extender thread) has chance to initialize <cm:property-placeholder>. It was achieved with OSGi listeners to initialize ConfigAdmin configurations just after felix configadmin bundle registers (service.pid=org.apache.felix.cm.ConfigurationAdmin) OSGi service but before blueprint.core bundle is started.

Even if OSGi specification says that relying on any order of events is bad idea, in camel-test-blueprint we used some tricks to force our listener to be called before any other listener waiting for initialization of (service.pid=org.apache.felix.cm.ConfigurationAdmin). This is how it works now:

main thread BP extender thread CM Event Dispatcher thread CM Configuration Updater thread
  • Preparation of test bundle containing @Test-annotated classes
  • Careful ordering of bundle descriptors to initialize Felix Connect OSGi Registry. Most of the bundles don't have any particular order, the only required sequence is:
    – Felix Connect itself (bundle "0")
    – test bundle
    – felix.configadmin
    – aries.blueprint.core
    this order is required to do correct synchronization of events and listener invocations to ensure proper ConfigAdmin initialization.
  • 2a if @Test class implements loadConfigAdminConfigurationFile(), org.osgi.service.cm.Configuration#update() is called with properties from specified file. Call is made early enough and we're 100% sure that it happens before Blueprint containers are created.
    – new properties are persisted by ConfigAdmin
    properties field is set to new value in org.apache.felix.cm.impl.ConfigurationImpl
  • Creation of Felix Connect registry - blueprint extender bundle starts threads that create Blueprint containers
  • listeners for ConfigurationEvent.CM_UPDATED event are notified (currently it's only fileinstall listener)
  • no ManagedService#updated() is called, or at least it's not relevant
  • expectReload flag is set if Blueprint XML contains <cm:property-placeholder update-strategy="reload">. This flag set to true means that each invocation of org.osgi.service.cm.Configuration#update() should be followed by waiting for Blueprint container reload event.
  • if @Test class implements useOverridePropertiesWithPropertiesComponent(), OSGi service OverrideProperties is registered
  • CamelBlueprintTestSupport waits for BlueprintEvent.CREATED event - for full initialization of Blueprint container
  • Blueprint XML with Camel context is parsed and run. Camel context itself isn't started yet
  • 2b <cm:property-placeholder> is initialized and ConfigAdmin configuration is fetched using PID configured with persistent-id="PID" attribute. These properties are initial properties of the resolver
  • <cm:property-placeholder> is registered to get notified when ConfigAdmin configuration for specified PID changes
  • setOverrideProperties() is called on PropertiesComponent if OverrideProperties service was found in OSGi registry
  • OSGi service related to Blueprint container is published in OSGi registry
  • BlueprintEvent.CREATED is emitted
  • 2c if @Test class implements useOverridePropertiesWithConfigAdmin(), org.osgi.service.cm.Configuration#update() is called with the overridden properties.
    – new properties are persisted by ConfigAdmin
    properties field is set to new value in org.apache.felix.cm.impl.ConfigurationImpl
  • if expectReload was true, CamelBlueprintTestSupport waits for BlueprintEvent.CREATED event again, because we know that Blueprint container will be reloaded (2nd time)
  • listeners for ConfigurationEvent.CM_UPDATED event are notified (currently it's only fileinstall listener)
  • ManagedService#updated() is called with updated configuration on every registered ManagedService OSGi service.
  • CmPropertyPlaceholder#updated() is called (in yet another thread, but it doesn't change the diagram).
  • CmPropertyPlaceholder (<cm:property-placeholder>) doesn't set properties field to updated properties, it just invokes blueprintContainer.reload()
  • Blueprint container is reloaded
  • 2d <cm:property-placeholder> is initialized and again, ConfigAdmin configuration contains changed values
  • BlueprintEvent.CREATED is emitted
  • Camel context is started
  • Camel routes are started
  • 6 Camel endpoint URIs are parsed and if they use property placeholders, Camel resolves them.
  • We don't have to wait for OSGi service related to Blueprint container because we've synchronized to BlueprintEvent.CREATED event already
  • @Test method is invoked

Important changes:

  • 2a2b This time we have correct synchronization, not with Blueprint events, but with OSGi listeners (Bundle and Service) so this sequence of events is always correct and <cm:property-placeholder> always sees ConfigAdmin configuration prepared by loadConfigAdminConfigurationFile() (if implemented).
  • 2c2d Correct synchronization. Blueprint container after reload will see updated ConfigAdmin configuration
  • 2a When <cm:property-placeholder> is initialized in first incarnation of Blueprint container, initial properties fetched from ConfigAdmin are always null. The only way of setting initial properties is to use <cm:default-properties>/<cm:property> subelements of <cm:property-placeholder>
  • 6 Careful synchronization of ConfigAdmin configuration updates and Blueprint events fixed the problem with all tests. We always know what exact properties will be used when resolving placeholders.
Summary of new scenarios
  • We've restored distinction between loadConfigAdminConfigurationFile() (initialization of ConfigAdmin configuration) and useOverridePropertiesWithConfigAdmin() (reloading of BlueprintContainer if update-strategy="reload") methods.

Summary

I hope this presentation of camel-test-blueprint internals will clear more confusion than it introduces and will be a good source of information in case you have any problems with org.apache.camel.test.blueprint.CamelBlueprintTestSupport based JUnit tests.

2014-08-29

Improving Performance of OpenShift VM

In previous post I've described how to install OSEoD-2.1.4-2.x86_64.vmdk image to run OpenShift Enterprise 2.1 on your Fedora/RHEL/CentOS machine using libvirt. There was information about:

  • resizing VM image
  • creating VM itself
  • configuring LVM to actually use additional VM storage
  • configuring OSE to use larger gear sizes
  • configuring static IP
  • configuring dev machine

However, after actually installing Fuse cartridge using openshift-origin-cartridge-fuse-6.1.0.redhat.390-2.el6op.noarch.rpm package, the performance was painfully slow, I wasn't able to create applications using fuse cartridge. Fabric was starting in more than 8 minutes...

I did a lot of tweaking, configuring, restarting, etc. and finally got to:

The final solution

Here's a list of tips to get RPM-based fuse cartridge running.

Optimizing VM image

In this post there's is solution to optimize qcow2 storage. I took original vmdk image and converted it into qcow2 format with preallocation set:

qemu-img convert -f vmdk -O qcow2 -o preallocation=metadata \
   /opt/vm/OSEoD-2.1.4-2.x86_64.vmdk \
   /opt/vm/OSEoD-2.1.4-2.x86_64-5.qcow2

(This time I've neither resized the image nor done any lvm/fs resizing. However I can't tell now whether this impacted the performance...)

Configuring VM

On VirtIO Disk 1 Configuration page I set:

  • Disk bus: VirtIO
  • Cache mode: none

After this, applications based in fuse cartridge where installed in less than 1 minute (which included starting the Fabric and waiting for io.fabric8.api.FabricService!).

2014-08-19

OpenShift Enterprise and DNS resolution

After installing OpenShift Enterprise using local Virtual Machine managed by libvirt we can access the applications running on OSE from host machine simply by adding correct nameserver to /etc/resolv.conf.

This will however break DNS name resolution inside guest machine. The reason is the way how libvirt set's up networking for guests.

libvirt runs dnsmasq DHCP and caching DNS server and it's visible from guest machines (by default) under 192.168.122.1 IPv4 address. Here's /etc/resolv.conf from guest machine:

# Generated by NetworkManager
search openshift.example.com
nameserver 127.0.0.1     # local named daemon configured to resolve names under openshift.example.com domain
nameserver 192.168.122.1 # DNS server from libvirt. this by default uses host's /etc/resolv.conf

If you add nameserver OSE guest IP to host's /etc/resolv.conf you may be able to resolve *.openshift.example.com addresses from your host, but it will prevent guest from resolving DNS names. Guest will try:

  • 127.0.0.1 which resolves only names under openshift.example.com domain
  • 192.168.122.1 - host's dnsmasq daemon which uses /etc/resolv.conf and ... tries guests DNS server first which fails to resolve names

I wasn't able to configure libvirt to specify other options for dnsmasq. The simplest thing to do is to restart dnsmasq daemon specyfying alternative DNS servers found in your /etc/resolv.conf without OSE DNS nameserver:

# ps -ef | grep dnsmasq | grep -v grep
nobody   32399     1  0 11:01 ?        00:00:00 /sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/default.conf
# kill $(pgrep dnsmasq)
# /sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/default.conf --local <one of nameservers from /etc/resolv.conf>

After this I was able to resolve both *.openshift.example.com names from host machine and all names from OSE VM.

2014-08-14

OpenShift Enterprise on Fedora 20

Do you want to play with OpenShift Enterprise by Red Hat on your Virtual Machine? Do you want to use Fedora 20 (or similar) without using VirtualBox? Here's an instruction. Please treat it as notes from successful installation, not as a comprehensive and detailed installation guide.

I used instructions provided by Kurt Stam in Notes on Getting started with OpenShift.

What you need to get

You need single VM image. I used OSEoD-2.1.4-2.x86_64.vmdk.

What you should have

You should have RHEL/Fedora/CentOS like system (I used Fedora 20) with some additional packages:

  • virt-manager (and related) to create/manage Virtual machines
  • qemu-img (and relaated) to tweak the original VM image

Resizing the image

When you check the image, you can see it has 20GB of virtual size.

$ qemu-img info OSEoD-2.1.4-2.x86_64.vmdk 
image: OSEoD-2.1.4-2.x86_64.vmdk
file format: vmdk
virtual size: 20G (21474836480 bytes)
disk size: 7.0G

Let's make it bigger. Because it's not possible to resize vmdk image (only raw images can be cconverted with qemu-img), we have to convert-resize-convert it:

$ qemu-img convert -p -f vmdk -O raw OSEoD-2.1.4-2.x86_64.vmdk OSEoD-2.1.4-2.x86_64.raw
    (100.00/100%)
$ qemu-img resize OSEoD-2.1.4-2.x86_64.raw +40G
Image resized.
$ qemu-img convert -p -f raw -O vmdk OSEoD-2.1.4-2.x86_64.raw OSEoD-2.1.4-2.x86_64-2.vmdk
    (100.00/100%)

Now we have 60GB virtual storage. Let's create Virtual Machine then.

Creating Virtual Machine

We can now create new VM by importing our resized VM image. Create new VM in Virtual Machine Manager and select Import existing disk image:

Select your image and choose OS Type and Version:

Set required Memory (RAM) and CPUs:

Finish. Ensure to customize configuration before install:

On configuration screen check Copy host CPU configuration and click Begin Installation:

Congratulations: you've just installed (actually: started) RedHat Enterprise Linux 6.5 with OpenShift Enterprise 2.1.4!

Configuring disk space

When we log into new VM using openshift login, we can check free space:

[openshift@vm ~]$ df -h
Filesystem            Size  Used Avail Use% Mounted on
/dev/mapper/vg_vm1-lv_root
                       18G  6.4G   10G  39% /
tmpfs                 5.0M   76K  5.0M   2% /dev/shm
/dev/vda1             485M   39M  421M   9% /boot

Let's use all available (60GB) space for root mount point. We could use the method described by Kurt Stam using GParted, or we can use LVM tools to resize lv_root logical volume. We'll do the latter.

Create new partition (we could use cfdisk to get more appealing visual experience or fdisk) with 8e type (Linux LVM). The resulting list of partitions should look like this:

   Device Boot      Start         End      Blocks   Id  System
/dev/vda1   *           3        1018      512000   83  Linux
/dev/vda2            1018       41611    20458496   8e  Linux LVM
/dev/vda3           41611      124831    41943040   8e  Linux LVM

Do not worry about Partition N does not end on cylinder boundary warnings. It may require to reboot the system to see /dev/vda3 device.

Add new physical volume:

[root@vm ~]# pvcreate /dev/vda3
  Physical volume "/dev/vda3" successfully created
[root@vm ~]# pvs
  PV         VG     Fmt  Attr PSize  PFree 
  /dev/vda2  vg_vm1 lvm2 a--  19.51g     0 
  /dev/vda3         lvm2 a--  40.00g 40.00g

Currently volume group vg_vm1 contains single physical volume and has VSize equal to 19.51g:

[root@vm ~]# vgs
  VG     #PV #LV #SN Attr   VSize  VFree
  vg_vm1   1   2   0 wz--n- 19.51g    0 

Add /dev/vda3 to the volume group:

[root@vm ~]# vgextend vg_vm1 /dev/vda3 
  Volume group "vg_vm1" successfully extended

This changed the size of volume group and the status of physical volumes:

[root@vm ~]# vgs
  VG     #PV #LV #SN Attr   VSize  VFree 
  vg_vm1   2   2   0 wz--n- 59.50g 40.00g
[root@vm ~]# pvs
  PV         VG     Fmt  Attr PSize  PFree 
  /dev/vda2  vg_vm1 lvm2 a--  19.51g     0 
  /dev/vda3  vg_vm1 lvm2 a--  40.00g 40.00g

Now we should resize logical volume:

[root@vm ~]# lvscan 
  ACTIVE            '/dev/vg_vm1/lv_root' [17.51 GiB] inherit
  ACTIVE            '/dev/vg_vm1/lv_swap' [2.00 GiB] inherit
[root@vm ~]# lvextend -l +100%FREE /dev/vg_vm1/lv_root
  Extending logical volume lv_root to 57.50 GiB
  Logical volume lv_root successfully resized
[root@vm ~]# lvscan 
  ACTIVE            '/dev/vg_vm1/lv_root' [57.50 GiB] inherit
  ACTIVE            '/dev/vg_vm1/lv_swap' [2.00 GiB] inherit

The last step is to actually resize the filesystem to expand all available logical volume size. Now we have only 9.9G free space:

[root@vm ~]# df -h
Filesystem            Size  Used Avail Use% Mounted on
/dev/mapper/vg_vm1-lv_root
                       18G  6.5G  9.9G  40% /
tmpfs                 2.9G  220K  2.9G   1% /dev/shm
/dev/vda1             485M   39M  421M   9% /boot

It's easy to resize live partition under LVM to get 48G free space:

[root@vm ~]# resize2fs -p /dev/mapper/vg_vm1-lv_root
resize2fs 1.41.12 (17-May-2010)
Filesystem at /dev/mapper/vg_vm1-lv_root is mounted on /; on-line resizing required
old desc_blocks = 2, new_desc_blocks = 4
Performing an on-line resize of /dev/mapper/vg_vm1-lv_root to 15074304 (4k) blocks.
The filesystem on /dev/mapper/vg_vm1-lv_root is now 15074304 blocks long.

[root@vm ~]# df -h
Filesystem            Size  Used Avail Use% Mounted on
/dev/mapper/vg_vm1-lv_root
                       57G  6.5G   48G  13% /
tmpfs                 2.9G  220K  2.9G   1% /dev/shm
/dev/vda1             485M   39M  421M   9% /boot

Now we're ready to configure OpenShift Enterprise itself.

Configuration of OpenShift Enterprise

As described here we can configure Openshift Enterprise to allow medium or even large gear sizes. Do the customization only to /etc/openshift/broker.conf file and alter demo user. After the configuration we should get this (on Openshift VM):

[root@vm ~]# oo-admin-ctl-user -l demo


User demo:
                            plan: 
                consumed domains: 0
                     max domains: 10
                  consumed gears: 0
                       max gears: 100
    max tracked storage per gear: 0
  max untracked storage per gear: 0
                       max teams: 10
viewing all global teams allowed: true
                      gear sizes: small, medium, large
            sub accounts allowed: false
private SSL certificates allowed: true
              inherit gear sizes: false
                      HA allowed: true

The above won't allow us however to install applications using medium or large gear sizes, because there's only one default district with small gear size.
We can however create entirely new district:

[root@vm ~]# oo-admin-ctl-district -c create -p "large" -n default
Successfully created district: 53f1f2e2e659c5a904000001

{"_id"=>"53f1f2e2e659c5a904000001",
 "uuid"=>"53f1f2e2e659c5a904000001",
 "available_uids"=>"<6000 uids hidden>",
 "name"=>"default",
 "platform"=>"linux",
 "gear_size"=>"large",
 "available_capacity"=>6000,
 "max_uid"=>6999,
 "max_capacity"=>6000,
 "active_servers_size"=>0,
 "updated_at"=>2014-08-18 12:34:43 UTC,
 "created_at"=>2014-08-18 12:34:43 UTC}

Deactivate & remove vm.openshift.example.com node from default-small district:

[root@vm ~]# oo-admin-ctl-district -c deactivate-node -n default-small -i vm.openshift.example.com
Success for node 'vm.openshift.example.com'!
...
[root@vm ~]# oo-admin-ctl-district -c remove-node -n default-small -i vm.openshift.example.com
Success for node 'vm.openshift.example.com'!
...

Configure node profile to allow large gear sizes. To do it, change /etc/openshift/resource_limits.conf to contain:

...
#
# Standard Profile
#
node_profile=large
...

And restart the broker:

[root@vm ~]# /etc/init.d/openshift-broker restart

Finally add the vm.openshift.example.com node to the new district (see that the gear_size parameter is set to large):

[root@vm ~]# oo-admin-ctl-district -c add-node -n default -i vm.openshift.example.com
Success for node 'vm.openshift.example.com'!


{"_id"=>"53f1f2e2e659c5a904000001",
 "active_servers_size"=>1,
 "available_capacity"=>6000,
 "available_uids"=>"<6000 uids hidden>",
 "created_at"=>2014-08-18 12:34:43 UTC,
 "gear_size"=>"large",
 "max_capacity"=>6000,
 "max_uid"=>6999,
 "name"=>"default",
 "platform"=>"linux",
 "servers"=>
  [{"_id"=>"53f1f76ce659c5080c000001",
    "active"=>true,
    "name"=>"vm.openshift.example.com",
    "unresponsive"=>false}],
 "updated_at"=>2014-08-18 12:34:43 UTC,
 "uuid"=>"53f1f2e2e659c5a904000001"}

Now we can remove previous district:

[root@vm ~]# oo-admin-ctl-district -c destroy -n default-small
!!!! WARNING !!!! WARNING !!!! WARNING !!!!
You are about to delete the default-small district.

This is NOT reversible, all remote data for this district will be removed.
Do you want to delete this district (y/n): y
Successfully deleted district: default-small

See the documentation for more information.

Configuration of networking

This is not of course necessary to get working environment, but let's switch from DHCP to static IP networking.

This is the content of /etc/sysconfig/network:

NETWORKING=yes
HOSTNAME=vm.openshift.example.com
GATEWAY=192.168.122.1

And /etc/sysconfig/network-scripts/ifcfg-eth0:

DEVICE="eth0"
BOOTPROTO="none"
IPADDR="192.168.122.105"
NETMASK="255.255.255.0"
NETWORK="192.168.122.0"
PEERDNS="yes"
DNS1="127.0.0.1"
DNS2="192.168.122.1"
IPV6INIT="yes"
MTU="1500"
NM_CONTROLLED="yes"
ONBOOT="yes"
TYPE="Ethernet"
UUID="0c56a8e2-4cda-4779-8cff-213584f3fc17"

The above configuration will use local DNS server as first DNS server and libvirt-provided one as second DNS server.

Ensure that /var/named/dynamic/openshift.example.com.db contains correct address for vm name:

...
ns1 IN A 127.0.0.1
vm A 192.168.122.105
activemq ...
...

And change /etc/openshift/node.conf to contain correct IP address of the node:

...
PUBLIC_HOSTNAME=vm.openshift.example.com
PUBLIC_IP=192.168.122.105
BROKER_HOST=vm.openshift.example.com
...

Configuration of developer machine

Networking

We will use host machine as the development machine, from which we will execute rhc command and manage OpenShift domains and application using web browser. First thing is to be able to access the machine using logical host names.

After switching to static-IP based networking, we can use guest's IP (here: 192.168.122.105) as DNS server - let's add it to host's /etc/resolve.conf:

$ cat /etc/resolv.conf 
# Generated by NetworkManager
...
nameserver 192.168.122.105
nameserver ...
nameserver ...
...

After this, we can resolve names under openshift.example.com domain:

$ dig vm.openshift.example.com

; <<>> DiG 9.9.4-P2-RedHat-9.9.4-15.P2.fc20 <<>> vm.openshift.example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 49858
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 2
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;vm.openshift.example.com. IN A

;; ANSWER SECTION:
vm.openshift.example.com. 1 IN A 192.168.122.105

;; AUTHORITY SECTION:
openshift.example.com. 1 IN NS ns1.openshift.example.com.

;; ADDITIONAL SECTION:
ns1.openshift.example.com. 1 IN A 127.0.0.1

;; Query time: 0 msec
;; SERVER: 192.168.122.105#53(192.168.122.105)
;; WHEN: Mon Aug 18 11:51:28 CEST 2014
;; MSG SIZE  rcvd: 103

rhc utility

When connected to vm.openshift.example.com VM, we will see browser page after logging in as openshift user. There's great deal of information about further steps. There's information about accessing OpenShift Enterprise console, using JBoss Developer Studio and rhc utility.

We won't use the VM as developer machine - let's use host machine for these tasks. To access OpenShift Enterprise from Fedora 20, we need rubygem-rhc package (although the package that provides rhc tool on RHEL is rhc-1.24.3.1-1.el6op.noarch).

Let's setup local environment:

$ rhc setup --server vm.openshift.example.com -l demo

After executing this command, we'll be asked to:

  • accept self-signed certificate
  • provide password for demo user
  • generate a token (stored in ~/.openshift/ directory)
  • upload existing (or generate new first) SSH keys to OpenShift Enterprise server
  • create domain (namespace) - let's create it later (using rhc create-domain command)

The above ends client side configuration. Now we're ready to create domain and applications inside OpenShift Enterprise.

Domain and first application

Using rhc create-domain or by pointing the browser to https://vm.openshift.example.com/console/settings URL, we can set a namespace for applications.

On https://vm.openshift.example.com/console/applications page, we can create applications using available cartridges and allowed gear sizes.

I could create for example php-5.4 based application using large gear size:

Verifying the installation

Let's clone the git repository of application and add simple phpinfo() test:

$ git clone ssh://53f1f8aae659c5d75d000001@php-test.openshift.example.com/~/git/php.git/
Cloning into 'php'...
remote: Counting objects: 17, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 17 (delta 0), reused 17 (delta 0)
Receiving objects: 100% (17/17), 17.95 KiB | 0 bytes/s, done.
Checking connectivity... done.
$ mv index.php index2.php 
$ cat > index.php
<?php
phpinfo();
?>
^D
$ git commit -a -m 'Checking phpinfo()'
[master fc57849] Checking phpinfo()
 1 file changed, 3 insertions(+), 274 deletions(-)
 rewrite index.php (100%)
$ git push origin master 
Counting objects: 5, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 309 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: Stopping PHP 5.4 cartridge (Apache+mod_php)
remote: Waiting for stop to finish
remote: Waiting for stop to finish
remote: Building git ref 'master', commit fc57849
remote: Checking .openshift/pear.txt for PEAR dependency...
remote: Preparing build for deployment
remote: Deployment id is bcbd60fe
remote: Activating deployment
remote: Starting PHP 5.4 cartridge (Apache+mod_php)
remote: Application directory "/" selected as DocumentRoot
remote: -------------------------
remote: Git Post-Receive Result: success
remote: Activation status: success
remote: Deployment completed with status: success
To ssh://53f1f8aae659c5d75d000001@php-test.openshift.example.com/~/git/php.git/
   0224a1e..fc57849  master -> master

Here's the final result after pointing your browser to http://php-test.openshift.example.com/:

Thank you very much for your attention.