Chapter 47. Fabric Component

Abstract

The Fabric component implements a location discovery mechanism for Apache Camel endpoints. This mechanism can also be used to provide load-balancing over a cluster of endpoints. On the client side (producer endpoints), endpoints are represented by an abstract ID and at run time, the ID is resolved to a specific endpoint URI. Because the URI is stored in a distributed registry (provided by Fuse Fabric), this enables you to create flexible applications whose topology can be specified at deploy time and updated dynamically.

Dependencies

The Fabric component can only be used in the context of a fabric-enabled Red Hat JBoss Fuse container. You must ensure that the fabric-camel feature is installed. In the context of Fabric, you install a feature by adding it to the relevant profile. For example, if you are using a profile called my-master-profile, you would add the fabric-camel feature by entering the following console command:
karaf@root> fabric:profile-edit --features fabric-camel my-master-profile
Note
To use the fabric component properly, make sure to add profiles like fabric-zookeeper and fabric-commands. However, If you are using jetty end-point, then include the camel-jetty feature.

URI format

A fabric endpoint has the following URI format:
fabric:ClusterID[:PublishedURI[?Options]]
The format of the URI depends on whether it is used to specify a consumer endpoint or a producer endpoint.
For a Fabric consumer endpoint, the URI format is:
fabric:ClusterID:PublishedURI[?Options]
Where the specified URI, PublishedURI, is published in the fabric registry and associated with the ClusterId cluster. The options, Options, are used when creating the consumer endpoint instance, but the options are not published with the PublishedURI in the fabric registry.
For a Fabric producer endpoint, the URI format is:
fabric:ClusterID
Where the client looks up the ID, ClusterId, in the fabric registry to discover the URI to connect to.

URI options

The Fabric component itself does not support any URI options. It is possible, however, to specify options for the published URI. These options are stored in the fabric registry as part of the URI and are used as follows:
  • Server-only options—options that are applicable only to the server are applied to the server endpoint (consumer endpoint) at run time.
  • Client-only options—options that are applicable only to the client are applied to the client endpoint (producer endpoint) at run time.
  • Common options—options common to the client and the server are applied to both.

Use cases for fabric endpoints

Fabric endpoints essentially provide a discovery mechanism for Apache Camel endpoints. For example, they support the following basic use cases:

Location discovery

Figure 47.1, “Location Discovery through Fabric” gives an overview of how Fabric endpoints enable location discovery at run time.

Figure 47.1. Location Discovery through Fabric

Location Discovery through Fabric
The server side of this application is defined by a route that starts with a Fabric endpoint, where the Fabric endpoint publishes the URI, jetty:http://0.0.0.0:9090. When this route is started, it automatically registers the Jetty URI in the fabric registry, under the cluster ID, foo.
The client side of the application is defined by a route that ends with the Fabric endpoint, fabric:foo. Now, when the client route starts, it automatically looks up the ID, foo, in the fabric registry and retrieves the associated Jetty endpoint URI. The client then creates a producer endpoint using the discovered Jetty URI and connects to the corresponding server port.

Load-balancing cluster

Figure 47.2, “Load Balancing through Fabric” gives an overview of how Fabric endpoints enable you to create a load-balancing cluster.

Figure 47.2. Load Balancing through Fabric

Load Balancing through Fabric
In this case, two Jetty servers are created, with the URIs, jetty:http://0.0.0.0:9090 and jetty:http://0.0.0.0:9191. Because these published URIs are both prefixed by fabric:foo:, both of the Jetty URIs are registered under the same cluster ID, foo, in the fabric registry.
Now, when the client routes starts, it automatically looks up the ID, foo, in the fabric registry. Because the foo ID is associated with multiple endpoint URIs, fabric implements a random load balancing algorithm to choose one of the available URIs. The client then creates a producer endpoint, using the chosen URI.

Auto-reconnect feature

Fabric endpoints support auto-reconnection. So, if a client endpoint (producer endpoint) loses its connection to a server endpoint, it will automatically go back to the fabric registry, ask for another URI, and then connect to the new URI.

Publishing an endpoint URI

To publish an endpoint URI, PublishedURI, in the fabric registry, define a fabric endpoint with the publisher syntax, FabricScheme:ClusterID:PublishedURI. Note that this syntax can only be used in a consumer endpoint (that is, an endpoint that appears in a from DSL command).
Example 47.1, “Publishing a URI ” shows a route that implements a Jetty HTTP server, where the Jetty URI is published to the fabric registry under the ID, cluster. The route is a simply HTTP server that returns the constant message, Response from Zookeeper agent, in the body of the HTTP response.

Example 47.1. Publishing a URI

<?xml version="1.0" encoding="UTF-8"?>
<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
           https://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">

 <bean id="fabric-camel" class="io.fabric8.camel.FabricComponent"/>

    <camelContext id="camel" trace="false" xmlns="http://camel.apache.org/schema/blueprint">
        <route id="fabric-server">
            <from uri="fabric-camel:cluster:jetty:http://0.0.0.0:9090/fabric"/>
            <log message="Request received : ${body}"/>
            <setHeader headerName="karaf.name">
                <simple>${sys.karaf.name}</simple>
            </setHeader>
            <transform>
                <simple>Response from Zookeeper agent</simple>
            </transform>
        </route>
    </camelContext>

</blueprint>
Note the following points about the preceding sample:
  • The Fabric component uses the CuratorFramework object to connect to the ZooKeeper server (Fabric registry), where the reference to the CuratorFramework object is provided automatically.
  • The from DSL command defines the fabric URI, fabric-camel:cluster:jetty:http://0.0.0.0:9090/fabric. At run time, this causes two things to happen:
    • The specified jetty URI is published to the fabric registry under the cluster ID, cluster.
    • The Jetty endpoint is activated and used as the consumer endpoint of the route (just as if it had been specified without the fabric-camel:cluster: prefix).
Because the route is implemented in blueprint XML, you would normally add the file containing this code to the src/main/resources/OSGI-INF/blueprint directory of a Maven project.

Looking up an endpoint URI

To look up a URI in the fabric registry, simply specify the fabric endpoint URI with an ID, in the format, FabricScheme:ClusterID. This syntax is used in a producer endpoint (for example, an endpoint that appears in a to DSL command).
Example 47.2, “Looking up a URI” shows a route that implements a HTTP client, where the HTTP endpoint is discovered dynamically at run time, by looking up the specified ID, cluster, in the fabric registry.

Example 47.2. Looking up a URI

<?xml version="1.0" encoding="UTF-8"?>
<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
           https://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">

 <bean id="fabric-camel" class="io.fabric8.camel.FabricComponent"/>

  <camelContext id="camel" trace="false" xmlns="http://camel.apache.org/schema/blueprint">

    <route id="fabric-client">
      <from uri="timer://foo?fixedRate=true&amp;period=10000"/>
      <setBody>
          <simple>Hello from Zookeeper server</simple>
      </setBody>
      <to uri="fabric-camel:cluster"/>
      <log message=">>> ${body} : ${header.karaf.name}"/>
    </route>

  </camelContext>

  <reference interface="org.apache.camel.spi.ComponentResolver"
              filter="(component=jetty)"/>

</blueprint>
Because the route is implemented in blueprint XML, you would normally add the file containing this code to the src/main/resources/OSGI-INF/blueprint directory of a Maven project.

Load-balancing example

In principle, implementing load balancing is easy using fabric endpoints. All that you have to do is to publish more than one endpoint URI under the same cluster ID. Now, when a client looks up that cluster ID, it gets a random selection out of the list of available endpoint URIs.
The servers in the load-balancing cluster have almost the same configuration. Essentially, the only difference between them is that they publish an endpoint URI with a different hostname and/or IP port. Instead of creating a separate OSGi bundle for every single server in the load-balancing cluster, however, it is better to define a template that enables you to specify the host or port using a configuration variable.
Example 47.3, “Server Template for a Load-Balancing Cluster” illustrates the template approach to defining servers in a load-balancing cluster.

Example 47.3. Server Template for a Load-Balancing Cluster

<?xml version="1.0" encoding="UTF-8"?>
<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.0.0"
           xsi:schemaLocation="
           http://www.osgi.org/xmlns/blueprint/v1.0.0
           https://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">

    <!-- osgi blueprint property placeholder -->
    <cm:property-placeholder
        id="myConfig"
        persistent-id="io.fabric8.examples.camel.loadbalancing.server"/>

    <bean id="fabric-camel" class="io.fabric8.camel.FabricComponent"/>

    <camelContext id="camel" trace="false" xmlns="http://camel.apache.org/schema/blueprint">
        <!-- using Camel properties component and refer
             to the blueprint property placeholder by its id -->
        <propertyPlaceholder id="properties"
                             location="blueprint:myConfig"
                             prefixToken="[[" suffixToken="]]"/>

        <route id="fabric-server">
            <from uri="fabric-camel:cluster:jetty:http://0.0.0.0:[[portNumber]]/fabric"/>
            <log message="Request received : ${body}"/>
            <setHeader headerName="karaf.name">
                <simple>${sys.karaf.name}</simple>
            </setHeader>
            <transform>
                <simple>Response from Zookeeper agent</simple>
            </transform>
        </route>
    </camelContext>

</blueprint>
First of all, you need to initialize the OSGi blueprint property placeholder. The property placeholder mechanism enables you to read property settings from the OSGi Config Admin service and substitute the properties in the blueprint configuration file. In this example, the property placeholder accesses properties from the io.fabric8.examples.camel.loadbalancing.server persistent ID. A persistent ID in the OSGi Config Admin service identifies a collection of related property settings. After initializing the property placeholder, you can access any property values from the persistent ID using the syntax, [[PropName]].
The Fabric endpont URI exploits the property placeholder mechanism to substitute the value of the Jetty port, [[portNumber]], at run time. At deploy time, you can specify the value of the portName property. For example, if using a custom feature, you could specify the property in the feature definition (see Add OSGi configurations to the feature). Alternatively, you can specify configuration properties when defining deployment profiles in the Fuse Management Console.

OSGi bundle plug-in configuration

When defining an OSGi bundle that uses Fabric endpoints, the Import-Package bundle header must be configured to import the following Java packages:
io.fabric8.zookeeper
For example, assuming that you use Maven to build your application, Example 47.4, “Maven Bundle Plug-In Configuration” shows how you can configure the Maven bundle plug-in to import the required packages.

Example 47.4. Maven Bundle Plug-In Configuration

<project ... >
  ...
  <build>
    <defaultGoal>install</defaultGoal>
    <plugins>
      ...
      <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <extensions>true</extensions>
        <configuration>
          <instructions>
            <Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
            <Import-Package>
              io.fabric8.zookeeper,
              *
            </Import-Package>
          </instructions>
        </configuration>
      </plugin>
    </plugins>
  </build>
  ...
</project>